{"version":3,"file":"main-1dd11a51b8fbc5e2e445.js","mappings":"UAAIA,ECCAC,EADAC,ECAAC,EACAC,E,8DCIG,MAAMC,EAAkB,IAEzBC,EAAwB,GAgBvB,SAASC,EAAmBC,GACjC,MAAOC,EAASC,IAAcC,EAAAA,EAAAA,UAA0BL,GAkBxD,OAhBAM,EAAAA,EAAAA,WAAU,KACR,IAAKJ,EAAS,OACd,IAAIK,GAAM,EACV,MAAMC,EAAO,MApBVC,iBACL,IACE,MAAMC,QAAUC,MAAM,kBACtB,OAAKD,EAAEE,SACOF,EAAEG,OADE,IAEpB,CAAE,MACA,OAAO,IACT,CACF,EAaWC,GAAsBC,KAAMC,KAC1BT,GAAOS,GAAGZ,EAAWY,MAG9BR,IACA,MAAMS,EAAQC,YAAYV,EAAMT,GAChC,MAAO,KACLQ,GAAM,EACNY,cAAcF,KAEf,CAACf,IAEGC,CACT,C,oGCrCA,MAAMiB,GAAiBC,EAAAA,EAAAA,GACrB,8bACA,CACEC,SAAU,CACRC,QAAS,CACPC,QAAS,yDACTC,YACE,oJACFC,QACE,wIACFC,UAAW,+DACXC,MAAO,uEACPC,KAAM,mDAERC,KAAM,CACJN,QAAS,iCACTO,GAAI,2FACJC,GAAI,gDACJC,GAAI,uCACJC,KAAM,UACN,UAAW,yDACX,UAAW,SACX,UAAW,YAGfC,gBAAiB,CACfZ,QAAS,UACTO,KAAM,aAKZ,SAASM,EAAO,G,IAAA,UACdC,EAAS,QACTd,EAAU,UAAS,KACnBO,EAAO,UAAS,QAChBQ,GAAU,KACPC,GALW,EAUd,MAAMC,EAAOF,EAAUG,EAAAA,GAAY,SAEnC,OACE,SAACD,EAAAA,CACCE,YAAU,SACVC,eAAcpB,EACdqB,YAAWd,EACXO,WAAWQ,EAAAA,EAAAA,IAAGzB,EAAe,CAAEG,UAASO,OAAMO,kBAC1CE,GAGV,C,6BCnDO,SAASO,EACdC,G,IACAC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAY,GACZ,UAAEC,GAAY,GAAd,uDAAiD,CAAC,EAElD,MAAMC,EAAQC,KAAKC,IAAIL,GAAQM,eAAe,QAAS,CACrDC,sBAAuBN,EACvBO,sBAAuBP,IAGzB,MAAI,WAAWQ,KAAKN,GAAeA,EAC/BH,EAAS,EAAU,IAAIG,IACpBD,GAAaF,EAAS,EAAI,IAAIG,IAAUA,CACjD,C,0JCoBO,MAAMO,EAA0C,CACrDC,KAAM,cACNC,OAAQ,sBAGJC,EAAU,CACd,UACA,WACA,QACA,QACA,MACA,OACA,OACA,WACA,YACA,UACA,WACA,YAIK,SAASC,EAAYC,GAC1B,MAAmB,QAAfA,EAA6B,MACd,QAAfA,EAA6B,MAC1B,QACT,CAEO,SAASC,EAAeC,EAAsBzC,GACnD,MAAO,YAAYA,KAAWyC,EAAMC,SAASD,EAAME,OACrD,CAEO,SAASC,EACdhE,EACA+D,EACAE,EACA7C,GAEA,IAAK2C,EAAO,OAAO,KACnB,MAAMG,EAAQlE,EAAQmE,OAAQtD,KAAQA,EAAEuD,OAAOhD,IAC/C,GAAI6C,EAAgB,CAClB,MAAMI,EAAIH,EAAMI,KACbzD,GAAkB,iBAAZA,EAAEiD,OAA4BjD,EAAEkD,MAAMQ,MAAM,EAAG,KAAOR,EAAMQ,MAAM,EAAG,IAE9E,GAAIF,EAAG,OAAOA,CAChB,CACA,IAAIG,EAA8B,KAClC,IAAK,MAAM3D,KAAKqD,EACE,WAAZrD,EAAEiD,OAAsBjD,EAAEkD,OAASA,KAAWS,GAAS3D,EAAEkD,MAAQS,EAAMT,SAAQS,EAAQ3D,GAE7F,OAAO2D,CACT,CAUA,MAAMC,EAA+B,CAAC,MAAO,MAAO,UAEpD,SAASC,EAAQC,EAAkBC,GACjC,OAAOD,EAAEZ,MAAMc,cAAcD,EAAEb,QAAUY,EAAEb,MAAMe,cAAcD,EAAEd,MACnE,CAOO,SAASgB,EACd9E,EACA+E,EACAC,EACA5D,G,IACA6D,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA6B,GAE7B,MAAMf,EAAQlE,EAAQmE,OAAQtD,KAAQA,EAAEuD,OAAOhD,IAAU8D,KAAKR,GACxDS,EAAiB,GACjBC,EAAO,CAACvB,EAAsBQ,KAClC,MAAMgB,EAAKzB,EAAeC,EAAOQ,GAC3BiB,EAAMzB,EAAMO,OAAOC,GACrBiB,IAAQH,EAAII,KAAMC,GAAMA,EAAEH,KAAOA,IAAKF,EAAIM,KAAK,CAAE5B,QAAOzC,QAASiD,EAAGgB,KAAIC,SAE9E,GAAIP,EAAM,CACR,MAAMW,EAAS9B,EAAemB,EAAM3D,GAC9BuE,EAAIzB,EAAM0B,UAAW/E,GAAM+C,EAAe/C,EAAGO,KAAasE,GAChE,GAAIC,GAAK,EAAG,IAAK,MAAME,IAAK,CAACF,EAAI,EAAGA,EAAGA,EAAI,GAAQzB,EAAM2B,IAAIT,EAAKlB,EAAM2B,GAAIzE,EAC9E,CACA,GAAI4D,EACF,IAAK,MAAMnE,KAAKb,EACd,IAAK,MAAMqE,KAAKI,EAAeb,EAAe/C,EAAGwD,KAAOW,GAAaI,EAAKvE,EAAGwD,GAEjF,MAAMyB,EAAa,IAAIC,IAAId,GAC3B,IAAK,MAAMpE,KAAKqD,EAAW4B,EAAWE,IAAIpC,EAAe/C,EAAGO,KAAWgE,EAAKvE,EAAGO,GAC/E,OAAO+D,EAAID,KAAK,CAACP,EAAGC,IAAMF,EAAQC,EAAEd,MAAOe,EAAEf,QAAUc,EAAEvD,QAAQyD,cAAcD,EAAExD,SACnF,CAOO,SAAS6E,EACdjG,EACA+E,EACA3D,EACA8E,EACAC,GAEA,IAAKpB,EAAM,MAAO,GAClB,MAAMb,EAAQlE,EAAQmE,OAAQtD,KAAQA,EAAEuD,OAAOhD,IAAU8D,KAAKR,GACxDgB,EAAS9B,EAAemB,EAAM3D,GAC9BuE,EAAIzB,EAAM0B,UAAW/E,GAAM+C,EAAe/C,EAAGO,KAAasE,GAChE,GAAIC,EAAI,EAAG,MAAO,GAClB,MAAMR,EAAgB,GACtB,IAAK,IAAIiB,EAAU,EAAGA,EAAUlC,EAAMmC,QAAUlB,EAAIkB,OAASF,EAAQC,IACnE,IAAK,MAAMP,IAAK,CAACF,EAAIS,EAAST,EAAIS,GAAU,CAC1C,MAAMvF,EAAIqD,EAAM2B,GAChB,IAAKhF,GAAKsE,EAAIkB,QAAUF,EAAQ,SAChC,MAAMd,EAAKzB,EAAe/C,EAAGO,GACxB8E,EAAWF,IAAIX,IAAKF,EAAIM,KAAKJ,EACpC,CAEF,OAAOF,CACT,CAGO,SAASmB,EACdC,EACAb,EACAc,GAEA,OAAKd,GACAa,GAAUA,IAAWb,GAAUc,EAAQd,GAAgBA,EACrDa,EAFa,IAGtB,CAQO,SAASE,EAAWC,GACzB,MAAOb,EAAGL,GAAKkB,EAAIC,MAAM,KACzB,MAAO,GAAGlD,EAAQmD,OAAOpB,GAAK,MAAMK,GACtC,CAUO,SAASgB,EAAcC,EAA2BC,GACvD,MAAMC,EAAkB,GAClBpC,EAVD,SAAmBkC,GACxB,OAAKA,GAAuB,SAAfA,EAAKhD,MACC,YAAfgD,EAAKhD,MAA4B,0BAClB,WAAfgD,EAAKhD,MAA2B,UAAUgD,EAAKG,MAAQH,EAAK/C,OAAOQ,MAAM,EAAG,IAAM,KAAK2C,OACpF,GAAGJ,EAAKhD,SAfV,SAAoB4C,GACzB,IAAKA,EAAK,MAAO,GACjB,MAAOb,EAAGL,EAAG2B,GAAKT,EAAInC,MAAM,EAAG,IAAIoC,MAAM,KACzC,MAAO,GAAGQ,KAAK3B,KAAKK,GACtB,CAW0BuB,CAAWN,EAAK/C,SAASmD,OAHN,IAI7C,CAKYG,CAAUP,GAGpB,OAFIlC,GAAGoC,EAAMvB,KAAqB,YAAhBqB,GAAMhD,MAAsBc,EAAI,kBAAkBA,KAChEmC,GAAUC,EAAMvB,KAAK,YAClBuB,EAAMM,KAAK,MACpB,CAEO,SAASC,EAAa1D,GAC3B,MAAO,kCAAkC4C,EAAW5C,EAAME,SACxC,iBAAhBF,EAAMC,MAA2B,aAAe,IAEpD,CAGO,SAAS0D,EAAsBxH,GACpC,MAAMmF,EAAoC,CAAC,EAC3C,IAAK,MAAMtE,KAAKb,EAAS,CACvB,GAAgB,WAAZa,EAAEiD,MAAoB,SAC1B,MAAM2D,EAAU,IAAI1B,IACpB,IAAK,MAAMnB,KAAK/D,EAAE6G,SACZ9C,EAAE+C,OAAsB,SAAZ/C,EAAEd,OAAgC,WAAZc,EAAEd,OAAqB2D,EAAQG,IAAIhD,EAAEd,OAEzE2D,EAAQ9F,OAAMwD,EAAItE,EAAEkD,MAAMQ,MAAM,EAAG,IAAM,IAAIkD,GAASvC,OAC5D,CACA,OAAOC,CACT,CAMO,SAAS0C,EAAc7H,GAC5B,IAAIwE,EAA8B,KAClC,IAAK,MAAM3D,KAAKb,EACE,WAAZa,EAAEiD,OAAsBjD,EAAEuD,OAAO0D,OAAStD,GAAS3D,EAAEkD,MAAQS,EAAMT,SAAQS,EAAQ3D,GAEzF,OAAO2D,GAAOJ,OAAO0D,IAAM,CAAExC,IAAKd,EAAMJ,OAAO0D,IAAK/E,MAAO0D,EAAWjC,EAAMT,QAAW,IACzF,C,qECnOA,MAAMgE,EAAM,8BAENC,EAAa,CAAEC,MAAO,MAAOC,MAAO,KAAMC,KAAM,IAAKC,KAAM,IAAKC,KAAM,IAEtEC,EAAkC,CACtCC,MAAO,CAAEC,QAAS,UAAWC,OAAQ,CAAC,CAAEC,QAAS,CAAC,CAAEC,YAAa,KAAO,CAAEC,UAAW,QACrFC,UAAW,CAAEL,QAAS,WACtBM,UAAW,CAAEN,QAAS,cAalBO,EAAU,IAAIC,ICbb,SAASC,EAAgBC,GAC9B,OAAKA,EACE,CACLC,QAAS,EACTC,QAAS,CACPC,QAAS,CACPC,KAAM,SACNC,MAAO,CAACL,EAAWM,SACnBC,SAAU,IACVC,YAAaR,EAAWQ,cAG5BC,OAAQ,CAAC,CAAEtE,GAAI,UAAWiE,KAAM,SAAUM,OAAQ,aAX5B,CAAET,QAAS,EAAGC,QAAS,CAAC,EAAGO,OAAQ,GAa7D,CAEO,SAASE,EAAoBR,GAClC,MAAMS,GAAMC,EAAAA,EAAAA,SAAQ,IDTf,WACL,MAAMC,GAD8BC,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAgBC,UACrCC,eAAe,wBAC9B,OAAOH,GAAII,YAAeC,KAAKC,MAAMN,EAAGI,aAA0B,EACpE,CCM4BG,GAAyB,KAC5CrB,EAAYsB,IAAiBtK,EAAAA,EAAAA,UAAkC,MAatE,OAZAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAOhB,ODZG,SAA8BX,EAAaT,GAChD,IAAIqB,EAAS3B,EAAQ4B,IAAItB,GAKzB,OAJKqB,IACHA,EAMJpK,eAA0BwJ,EAAaT,GACrC,MAAMxI,EAAI+J,mBAAmBd,GACvBe,QAAuBrK,MAAM,GAAGuH,0BAA4BlH,IAAK,CACrEiK,OAAQ,OACRC,QAAS,CAAE,eAAgB,oBAC3BC,KAAMX,KAAKY,UAAU,IAAK3C,EAAOe,GAAU6B,SAAU,QAASC,OAAQ,SAExE,IAAKN,EAAepK,GAClB,MAAM,IAAI2K,MAAM,uCAAuCP,EAAeQ,UAExE,MAAM,QAAEC,SAAmBT,EAAenK,OACpC6K,EAAIX,mBAAmBU,IAEvB,MAAErD,EAAK,MAAEC,EAAK,KAAEC,EAAI,KAAEC,EAAI,KAAEC,GAASL,EACrCwD,QAAiBhL,MACrB,GAAGuH,8BAAgCwD,SAAS1K,UACjCwH,WAAcJ,WAAeC,UAAcC,UAAaC,KAErE,IAAKoD,EAAS/K,GACZ,MAAM,IAAI2K,MAAM,kCAAkCI,EAASH,UAE7D,MAAM,UAAEI,SAAqBD,EAAS9K,OAEtC,MAAO,CACL8I,QAAS,GAAGzB,oCAAsCwD,SAAS1K,IAC3D6I,YAAa+B,GAAa,gBAE9B,CAjCaC,CAAW5B,EAAKT,GACzBN,EAAQ4C,IAAItC,EAASqB,IAEhBA,CACT,CCDIkB,CAAqB9B,EAAKT,GAASzI,KAChCiL,IACMpB,GAAWD,EAAcqB,IAE/BC,GAAiBC,QAAQC,MAAM,iCAAkCF,IAE7D,KACLrB,GAAY,IAEb,CAACX,EAAKT,IACFH,CACT,C,8FCVsB+C,OAAOC,QANkB,CAC7CC,OAAQ,OACRC,UAAW,UACXC,OAAQ,OACRC,QAAS,U,cCmDJ,MCpBMC,EAAuB,I,IAAA,cAClCC,EAAa,QACbC,EAAO,KACPC,EAAI,MACJC,EAAK,MACLC,GAGD,EACC,IAAKH,EAAS,CACZ,MAAMI,GACJL,EAAgBI,EAAQA,EAAME,IAAKzI,GAAMuG,mBAAmBvG,KAC5DiD,KAtCiC,CAACqF,IACtC,OAAQA,GACN,IAAK,OAML,QACE,MAAO,IALT,IAAK,gBACH,MAAO,IACT,IAAK,iBACH,MAAO,QA+BFI,CAAwBJ,IAC/B,OAAQA,GACN,IAAK,QACH,MAAO,IAAIE,IACb,IAAK,SACH,MAAO,IAAIH,KAAQG,IACrB,IAAK,SACH,OAAOA,EACT,QACE,MAAO,GAAGH,KAAQG,IAExB,CAEA,MAAMG,EAhE6B,CAACL,IACpC,OAAQA,GACN,IAAK,QACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,QACE,MAAO,MAuDOM,CAAsBN,GAClCE,EAAeD,EAClBE,IAAKzI,GACU,UAAVsI,GAA+B,WAAVA,EAChBH,EAAgBnI,EAAIuG,mBAAmBvG,GAGzC6I,EAAwB,CAC7BV,gBACAE,OACAE,MAAOvI,KAGViD,KAAK0F,GACR,MAAiB,UAAVL,GAA+B,WAAVA,EACxBK,EAAYH,EACZA,GAGOK,EAA2B,I,IAAA,cACtCV,EAAa,KACbE,EAAI,MACJE,GACwB,EACxB,GAAIA,QACF,MAAO,GAGT,GAAqB,iBAAVA,EACT,MAAM,IAAIxB,MACR,wGAIJ,MAAO,GAAGsB,KAAQF,EAAgBI,EAAQhC,mBAAmBgC,MAGlDO,EAAwB,I,IAAA,cACnCX,EAAa,QACbC,EAAO,KACPC,EAAI,MACJC,EAAK,MACLC,EAAK,UACLQ,GAID,EACC,GAAIR,aAAiBS,KACnB,OAAOD,EAAYR,EAAMU,cAAgB,GAAGZ,KAAQE,EAAMU,gBAG5D,GAAc,eAAVX,IAA2BF,EAAS,CACtC,IAAIc,EAAmB,GACvBtB,OAAOC,QAAQU,GAAOY,QAAS,I,IAAC1D,EAAKzF,GAAE,EACrCkJ,EAAS,IACJA,EACHzD,EACA0C,EAAiBnI,EAAeuG,mBAAmBvG,MAGvD,MAAMwI,EAAeU,EAAOjG,KAAK,KACjC,OAAQqF,GACN,IAAK,OACH,MAAO,GAAGD,KAAQG,IACpB,IAAK,QACH,MAAO,IAAIA,IACb,IAAK,SACH,MAAO,IAAIH,KAAQG,IACrB,QACE,OAAOA,EAEb,CAEA,MAAMG,EAhH8B,CAACL,IACrC,OAAQA,GACN,IAAK,QACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,QACE,MAAO,MAuGOc,CAAuBd,GACnCE,EAAeZ,OAAOC,QAAQU,GACjCE,IAAK,I,IAAChD,EAAKzF,GAAE,E,OACZ6I,EAAwB,CACtBV,gBACAE,KAAgB,eAAVC,EAAyB,GAAGD,KAAQ5C,KAASA,EACnD8C,MAAOvI,MAGViD,KAAK0F,GACR,MAAiB,UAAVL,GAA+B,WAAVA,EACxBK,EAAYH,EACZA,GCpKOa,EAAgB,cAoGtB,SAASC,EAAoBC,GAKlC,MAAMC,OAA2BC,IAAjBF,EAAQ5C,KAGxB,OAFyB6C,GAAWD,EAAQG,eAGtC,mBAAoBH,OAEOE,IAA3BF,EAAQI,gBAA2D,KAA3BJ,EAAQI,eAEvBJ,EAAQI,eAAiB,KAI9B,KAAjBJ,EAAQ5C,KAAc4C,EAAQ5C,KAAO,KAI1C6C,EACKD,EAAQ5C,UADjB,CAMF,CCzHO,MAAMiD,EAAe3N,MAC1B4N,EACAC,KAEA,MAAMC,EACgB,mBAAbD,QAAgCA,EAASD,GAAQC,EAE1D,GAAKC,EAIL,MAAoB,WAAhBF,EAAKG,OACA,UAAUD,IAGC,UAAhBF,EAAKG,OACA,SAASC,KAAKF,KAGhBA,GC5BIG,EAAwB,W,IAAc,cACjD/B,EAAa,MACbgC,EAAK,OACLC,GAC0B,UAAH,6CAAG,CAAC,EA2C3B,OA1CyBC,IACvB,MAAMC,EAAmB,GACzB,GAAID,GAAsC,iBAAhBA,EACxB,IAAK,MAAMhC,KAAQgC,EAAa,CAC9B,MAAM9B,EAAQ8B,EAAYhC,GAE1B,GAAIE,QAIJ,GAAIgC,MAAMC,QAAQjC,GAAQ,CACxB,MAAMkC,EAAkBvC,EAAoB,CAC1CC,gBACAC,SAAS,EACTC,OACAC,MAAO,OACPC,WACG4B,IAEDM,GAAiBH,EAAOlJ,KAAKqJ,EACnC,MAAO,GAAqB,iBAAVlC,EAAoB,CACpC,MAAMmC,EAAmB5B,EAAqB,CAC5CX,gBACAC,SAAS,EACTC,OACAC,MAAO,aACPC,MAAOA,KACJ6B,IAEDM,GAAkBJ,EAAOlJ,KAAKsJ,EACpC,KAAO,CACL,MAAMC,EAAsB9B,EAAwB,CAClDV,gBACAE,OACAE,MAAOA,IAELoC,GAAqBL,EAAOlJ,KAAKuJ,EACvC,CACF,CAEF,OAAOL,EAAOrH,KAAK,KAGvB,EAEM2H,EAAoB,CACxBrB,EAGAlB,MAEKA,QAGDA,KAAQkB,EAAQ7C,WAAW6C,EAAQsB,QAAQxC,SAI7C,WAAYkB,EAAQ7C,WACpB6C,EAAQ7C,QAAgB,QACa,iBAA9B6C,EAAQ7C,QAAgB,SAExB6C,EAAQ7C,QAAgB,OAAEoE,SAAS,GAAGzC,OAgDpC0C,EAAgCxB,IAC3C,MAAMyB,EAAkBzB,EAAQ0B,OAAOC,UAAUC,QAOjD,MFhDoB,CAAC,I,IAAA,QACrBC,EAAO,KACPC,EAAI,MACJR,EAAK,gBACLS,EACArK,IAAKsK,GAON,EAEKtK,GAAOmK,GAAW,KADNG,EAAKC,WAAW,KAAOD,EAAO,IAAIA,KAE9CF,IACFpK,EAtFiC,CAAC,I,IAAA,KAAEoK,EAAMpK,IAAKsK,GAAsB,EACnEtK,EAAMsK,EACV,MAAME,EAAUF,EAAKG,MAAMrC,GAC3B,GAAIoC,EACF,IAAK,MAAMC,KAASD,EAAS,CAC3B,IAAIrD,GAAU,EACVC,EAAOqD,EAAMC,UAAU,EAAGD,EAAM1J,OAAS,GACzCsG,EAA6B,SAE7BD,EAAKuD,SAAS,OAChBxD,GAAU,EACVC,EAAOA,EAAKsD,UAAU,EAAGtD,EAAKrG,OAAS,IAGrCqG,EAAKmD,WAAW,MAClBnD,EAAOA,EAAKsD,UAAU,GACtBrD,EAAQ,SACCD,EAAKmD,WAAW,OACzBnD,EAAOA,EAAKsD,UAAU,GACtBrD,EAAQ,UAGV,MAAMC,EAAQ8C,EAAKhD,GAEnB,GAAIE,QACF,SAGF,GAAIgC,MAAMC,QAAQjC,GAAQ,CACxBtH,EAAMA,EAAI4K,QACRH,EACAxD,EAAoB,CAAEE,UAASC,OAAMC,QAAOC,WAE9C,QACF,CAEA,GAAqB,iBAAVA,EAAoB,CAC7BtH,EAAMA,EAAI4K,QACRH,EACA5C,EAAqB,CACnBV,UACAC,OACAC,QACAC,MAAOA,EACPQ,WAAW,KAGf,QACF,CAEA,GAAc,WAAVT,EAAoB,CACtBrH,EAAMA,EAAI4K,QACRH,EACA,IAAI7C,EAAwB,CAC1BR,OACAE,MAAOA,OAGX,QACF,CAEA,MAAMuD,EAAevF,mBACT,UAAV+B,EAAoB,IAAIC,IAAqBA,GAE/CtH,EAAMA,EAAI4K,QAAQH,EAAOI,EAC3B,CAEF,OAAO7K,GAmBC8K,CAAsB,CAAEV,OAAMpK,SAEtC,IAAIqJ,EAASO,EAAQS,EAAgBT,GAAS,GAO9C,OANIP,EAAOkB,WAAW,OACpBlB,EAASA,EAAOqB,UAAU,IAExBrB,IACFrJ,GAAO,IAAIqJ,KAENrJ,GEuBA+K,CAAO,CACZZ,QALE7B,EAAQ4B,SAAsC,iBAApB5B,EAAQ4B,QAChC5B,EAAQ4B,QACRH,EAIJK,KAAM9B,EAAQ8B,KAEdR,MAAQtB,EAAQ0C,sBAAmCxC,EAAhBF,EAAQsB,MAC3CS,gBACqC,mBAA5B/B,EAAQ+B,gBACX/B,EAAQ+B,gBACRpB,EAAsBX,EAAQ+B,iBACpCrK,IAAKsI,EAAQtI,OAIJiL,EAAe,CAAC5L,EAAWC,KACtC,MAAM4L,EAAS,IAAK7L,KAAMC,GAE1B,OADA4L,EAAOzF,QAAU0F,EAAa9L,EAAEoG,QAASnG,EAAEmG,SACpCyF,GAMIE,EAAuB,CAClC,SACA,SACA,MACA,OACA,QACA,OACA,OAGWD,EAAe,W,2BACvB1F,EAAAA,IAAAA,MAAAA,GAAAA,EAAAA,EAAAA,EAAAA,EAAAA,IAAAA,EAAAA,GAAAA,UAAAA,GAEH,MAAM4F,EAAsC,CAAC,EAC7C,IAAK,MAAMC,KAAU7F,EAAS,CAC5B,IAAK6F,GAA4B,iBAAXA,EACpB,SAGF,MAAMC,EAAW5E,OAAOC,QAAQ0E,GAEhC,IAAK,MAAO9G,EAAK8C,KAAUiE,EACzB,GACEH,EAAqBvB,SACnBrF,IAEe,iBAAV8C,EAEP+D,EAAc7G,GAAO,IACf6G,EAAc7G,MACf8C,QAEA,GAAc,OAAVA,SACF+D,EAAc7G,QAChB,GAAI8E,MAAMC,QAAQjC,GACvB,IAAK,MAAMvI,KAAKuI,EAEd+D,EAAc7G,GAAO,IAAK6G,EAAc7G,IAAQ,GAAKzF,aAEpCyJ,IAAVlB,IAGT+D,EAAc7G,GACK,iBAAV8C,EAAqBvC,KAAKY,UAAU2B,GAAUA,EAG7D,CACA,OAAO+D,CACT,EAEaG,EAAe,W,MAEoB,IAD9CC,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAqD,CAAC,E,ECjM3CC,ECEe,W,IAGtBC,EAHuBT,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAiB,CAAC,EACzCU,EAAUX,EAAaO,IAAgBN,GAI3C,GAAIU,EAAQ5B,SAAW,UAAW4B,EAAQ5B,OACxC2B,EAAWC,EAAQ5B,UACd,CAEL,MAAM,KAAEpB,KAASiD,GAAsBD,EACvCD,EAAW3B,EAAAA,EAAM8B,OAAOD,EAC1B,CAEA,MAAME,EAAY,KAAe,IAAKH,IAahCI,EAAgBhR,MAAOsN,IAC3B,MAAM2D,EAAO,IACRL,KACAtD,EACH0B,MAAO1B,EAAQ0B,OAAS4B,EAAQ5B,OAAS2B,EACzClG,QAAS0F,EAAaS,EAAQnG,QAAS6C,EAAQ7C,UAoBjD,OAjBIwG,EAAKC,eFiCgBlR,OAAO,I,IAAA,SAClCkR,KACG5D,GAIF,EACD,IAAK,MAAMM,KAAQsD,EAAU,CAC3B,GAAIvC,EAAkBrB,EAASM,EAAKxB,MAClC,SAEF,MAAM0B,QAAcH,EAAaC,EAAMN,EAAQM,MAE/C,IAAKE,EACH,SAGF,MAAM1B,EAAOwB,EAAKxB,MAAQ,gBAE1B,OAAQwB,EAAKuD,IACX,IAAK,QACE7D,EAAQsB,QACXtB,EAAQsB,MAAQ,CAAC,GAEnBtB,EAAQsB,MAAMxC,GAAQ0B,EACtB,MACF,IAAK,SAAU,CACb,MAAMxB,EAAQ,GAAGF,KAAQ0B,IACrB,WAAYR,EAAQ7C,SAAW6C,EAAQ7C,QAAgB,OACzD6C,EAAQ7C,QAAgB,OAAI,GAAG6C,EAAQ7C,QAAgB,WAAM6B,IAE7DgB,EAAQ7C,QAAgB,OAAI6B,EAE9B,KACF,CAEA,QACEgB,EAAQ7C,QAAQ2B,GAAQ0B,EAG9B,GExEUsD,CAAc,IACfH,EACHC,SAAUD,EAAKC,WAIfD,EAAKI,wBACDJ,EAAKI,iBAAiBJ,QAGZzD,IAAdyD,EAAKvG,MAAsBuG,EAAKxD,iBAClCwD,EAAKvG,KAAOuG,EAAKxD,eAAewD,EAAKvG,OAKhC,CAAEuG,OAAMjM,IAFH8J,EAASmC,KAMjBK,EAA6BtR,MAAOsN,IAExC,MAAM,KAAE2D,EAAI,IAAEjM,SAAcgM,EAAc1D,GAC1C,IAEE,MAAMiE,EAASN,EAAKjC,OAEd,KAAEpB,KAAS4D,GAAoBP,EAC/BQ,QAAiBF,EAAO,IACzBC,EACHtC,QAAS,GACTwC,KAAMrE,EAAoB4D,GAC1BxG,QAASwG,EAAKxG,QAEdkH,OAAQV,EAAKjB,iBAAmBiB,EAAKrC,WAAQpB,EAC7CxI,QAGF,IAAI,KAAE0M,GAASD,EAYf,MAV0B,SAAtBR,EAAKW,eACHX,EAAKY,yBACDZ,EAAKY,kBAAkBH,GAG3BT,EAAKa,sBACPJ,QAAaT,EAAKa,oBAAoBJ,KAInC,IACFD,EACHC,KAAMA,GAAQ,CAAC,EAEnB,CAAE,MAAOhG,GACP,MAAMqG,EAAIrG,EACV,GAAIuF,EAAKe,aACP,MAAMD,EAIR,OADAA,EAAErG,MAAQqG,EAAEN,UAAUC,MAAQ,CAAC,EACxBK,CACT,GAGIE,EACHzH,GAAmC8C,GAClCgE,EAAQ,IAAKhE,EAAS9C,WAEpB0H,EACH1H,GAAkCxK,MAAOsN,IACxC,MAAM,KAAE2D,EAAI,IAAEjM,SAAcgM,EAAc1D,GAC1C,MNpCyB,K,IAazB6E,GAb2C,UAC/CC,EAAS,WACTC,EAAU,WACVC,EAAU,oBACVR,EAAmB,kBACnBD,EAAiB,qBACjBU,EAAoB,oBACpBC,EAAmB,iBACnBC,EAAgB,WAChBC,EAAU,IACV1N,KACGsI,GACqB,EAGxB,MAAMqF,EACJD,GACC,CAACE,GAAe,IAAIC,QAASC,GAAYC,WAAWD,EAASF,KA6JhE,MAAO,CAAEI,OA3JYhT,kBACnB,IAAIiT,EAAqBV,GAAwB,IAC7CW,EAAU,EACd,MAAMC,EAAS7F,EAAQ6F,SAAU,IAAIC,iBAAkBD,OAEvD,MACMA,EAAOE,SADA,CAGXH,IAEA,MAAMzI,EACJ6C,EAAQ7C,mBAAmB6I,QACvBhG,EAAQ7C,QACR,IAAI6I,QAAQhG,EAAQ7C,cAEN+C,IAAhB2E,GACF1H,EAAQY,IAAI,gBAAiB8G,GAG/B,IACE,MAAMoB,EAA2B,CAC/BC,SAAU,YACPlG,EACH5C,KAAM4C,EAAQI,eACdjD,UACA0I,UAEF,IAAI7B,EAAU,IAAImC,QAAQzO,EAAKuO,GAC3BnB,IACFd,QAAgBc,EAAUpN,EAAKuO,IAIjC,MAAMG,EAASpG,EAAQpN,OAASyT,WAAWzT,MACrCuR,QAAiBiC,EAAOpC,GAE9B,IAAKG,EAAStR,GACZ,MAAM,IAAI2K,MACR,eAAe2G,EAAS1G,UAAU0G,EAASmC,cAG/C,IAAKnC,EAAS/G,KAAM,MAAM,IAAII,MAAM,2BAEpC,MAAM+I,EAASpC,EAAS/G,KACrBoJ,YAAY,IAAIC,mBAChBC,YAEH,IAAIC,EAAS,GAEb,MAAMC,EAAe,KACnB,IACEL,EAAOM,QACT,CAAE,MAEF,GAGFhB,EAAOiB,iBAAiB,QAASF,GAEjC,IACE,OAAa,CACX,MAAM,KAAEG,EAAI,MAAE/H,SAAgBuH,EAAOS,OACrC,GAAID,EAAM,MACVJ,GAAU3H,EAEV,MAAMiI,EAASN,EAAO5N,MAAM,QAC5B4N,EAASM,EAAOC,OAAS,GAEzB,IAAK,MAAMC,KAASF,EAAQ,CAC1B,MAAMG,EAAQD,EAAMpO,MAAM,MACpBsO,EAA2B,GACjC,IAAIC,EAoBAlD,EAlBJ,IAAK,MAAMmD,KAAQH,EACjB,GAAIG,EAAKtF,WAAW,SAClBoF,EAAUxP,KAAK0P,EAAKjF,QAAQ,YAAa,UACpC,GAAIiF,EAAKtF,WAAW,UACzBqF,EAAYC,EAAKjF,QAAQ,aAAc,SAClC,GAAIiF,EAAKtF,WAAW,OACzB4C,EAAc0C,EAAKjF,QAAQ,UAAW,SACjC,GAAIiF,EAAKtF,WAAW,UAAW,CACpC,MAAMuF,EAASxO,OAAOyO,SACpBF,EAAKjF,QAAQ,aAAc,IAC3B,IAEGtJ,OAAO0O,MAAMF,KAChB7B,EAAa6B,EAEjB,CAIF,IAAIG,GAAa,EAEjB,GAAIN,EAAU5O,OAAQ,CACpB,MAAMmP,EAAUP,EAAU3N,KAAK,MAC/B,IACE0K,EAAO3H,KAAKC,MAAMkL,GAClBD,GAAa,CACf,CAAE,MACAvD,EAAOwD,CACT,CACF,CAEID,IACEpD,SACIA,EAAkBH,GAGtBI,IACFJ,QAAaI,EAAoBJ,KAIrCY,IAAa,CACXZ,OACAyD,MAAOP,EACP7P,GAAIoN,EACJiD,MAAOnC,IAGL0B,EAAU5O,eACN2L,EAEV,CACF,CACF,CAAE,QACAyB,EAAOkC,oBAAoB,QAASnB,GACpCL,EAAOyB,aACT,CAEA,KACF,CAAE,MAAO5J,GAIP,GAFA2G,IAAa3G,QAGa8B,IAAxBgF,GACAU,GAAWV,EAEX,MAIF,MAAM+C,EAAU7S,KAAK8S,IACnBvC,EAAa,IAAMC,EAAU,GAC7BT,GAAoB,WAEhBE,EAAM4C,EACd,CACF,CACF,CAEeE,KMxIJC,CAAgB,IAClBzE,EACHvG,KAAMuG,EAAKvG,KACXD,QAASwG,EAAKxG,QACdD,SAEA2I,OAAQlC,EAAKkC,OACbnO,SAIN,MAAO,CACL8J,SAAQA,EACR6G,QAAS1D,EAAa,WACtB2D,OAAQ3D,EAAa,UACrB5H,IAAK4H,EAAa,OAClBlB,YACA8E,KAAM5D,EAAa,QACnBtB,WACArD,QAAS2E,EAAa,WACtB6D,MAAO7D,EAAa,SACpB8D,KAAM9D,EAAa,QACnB+D,IAAK/D,EAAa,OAClBX,UACA2E,UApHiB/F,IACjBU,EAAUX,EAAaW,EAASV,GAChCS,EAAS1B,SAAW,IACf0B,EAAS1B,YACT2B,EAEHnG,QAAS0F,EAAaQ,EAAS1B,SAASxE,QAASmG,EAAQnG,UAEpDsG,KA6GPmF,IAAK,CACHP,QAASzD,EAAU,WACnB0D,OAAQ1D,EAAU,UAClB7H,IAAK6H,EAAU,OACf2D,KAAM3D,EAAU,QAChB5E,QAAS4E,EAAU,WACnB4D,MAAO5D,EAAU,SACjB6D,KAAM7D,EAAU,QAChB8D,IAAK9D,EAAU,OACfiE,MAAOjE,EAAU,UAEnBiE,MAAOlE,EAAa,SAExB,CDnJsBmE,CAAa5F,K,cEVnC,MAAM6F,EAAarH,EAAAA,EAAM8B,OAAO,CAC9B5B,QAAS,uBACTzE,QAAS,CAAE,eAAgB,oBAC3B6L,iBAAiB,IAWZtW,eAAeuW,IACpB,IACE,MAAMC,QAAYH,EAAWhM,IAAI,iBACjC,OAAOmM,EAAI9E,MAAMA,MAAM+E,MAAQ,IACjC,CAAE,MACA,OAAO,IACT,CACF,CAfAJ,EAAWK,aAAapF,QAAQqF,IAAKrF,IACnC,MAAM,UAAEsF,IAAcC,EAAAA,EAAAA,IAAYjN,SAASkN,QAI3C,OAHIxF,EAAQ7G,SAAWmM,IACrBtF,EAAQ7G,QAAQ,eAAiBmM,GAE5BtF,ICFT,MAAMyF,GAAcC,EAAAA,EAAAA,eAAuC,MAEpD,SAASC,EAAa,G,IAAA,SAAEC,GAAF,EAC3B,MAAOT,EAAMU,IAAWvX,EAAAA,EAAAA,UAA0B,OAC3CwX,EAAWC,IAAgBzX,EAAAA,EAAAA,WAAS,IAE3CC,EAAAA,EAAAA,WAAU,KACRyX,IAAqBhX,KAAMiX,IACzBJ,EAAQI,GACRF,GAAa,MAEd,IAEH,MAAMG,GAAQC,EAAAA,EAAAA,aAAYzX,MAAO0X,EAAeC,KAC9C,MAAMJ,QDAHvX,eAAqB0X,EAAeC,GACzC,IAEE,aADkBtB,EAAWN,KAAK,cAAe,CAAE2B,QAAOC,cAC/CjG,KAAKA,KAAK+E,IACvB,CAAE,MAAO/K,GACP,GAAIsD,EAAAA,EAAM4I,aAAalM,GAAQ,CAC7B,GAA+B,MAA3BA,EAAM+F,UAAU1G,OAAgB,CAClC,MAAM8M,QAAoBtB,IAC1B,GAAIsB,EACF,OAAOA,CAEX,CACA,GAAInM,EAAM+F,UAAUC,KAAM,CACxB,MAAMhH,EAAOgB,EAAM+F,SAASC,KAC5B,GAAIhH,EAAKoN,QAAQ/R,OACf,MAAM,IAAI+E,MAAMJ,EAAKoN,OAAO,GAAGC,QAEnC,CACF,CACA,MAAM,IAAIjN,MAAM,yCAClB,CACF,CCrBoBwM,CAAcI,EAAOC,GACrCR,EAAQI,IACP,IAEGS,GAASP,EAAAA,EAAAA,aAAYzX,gBDmBtBA,iBACL,UACQqW,EAAWT,OAAO,gBAC1B,CAAE,MAAOlK,GAGP,IAAKsD,EAAAA,EAAM4I,aAAalM,IAAqC,MAA3BA,EAAM+F,UAAU1G,OAChD,MAAMW,CAEV,CACF,CC5BU4L,GACNH,EAAQ,OACP,IAEH,OACE,SAACJ,EAAYkB,SAAQ,CAAC3L,MAAO,CAAEmK,OAAMW,YAAWI,QAAOQ,U,SACpDd,GAGP,CAEO,SAASgB,IACd,MAAMC,GAAMC,EAAAA,EAAAA,YAAWrB,GACvB,IAAKoB,EACH,MAAM,IAAIrN,MAAM,+CAElB,OAAOqN,CACT,C,mECHA,MAAME,EAAgD,CACpD,EAAG,UACH,EAAG,UACH,EAAG,UACH,EAAG,WAiCE,SAASC,EAAW,G,IAAA,QACzBC,EAAO,MACPC,EAAK,KACLC,EAAI,KACJC,EAAI,SACJC,EAAQ,OACRC,EAAM,QACNC,EAAO,KACPxX,GARyB,EAUzB,GAAqB,IAAjBmX,EAAMzS,OACR,OACE,SAAC+S,MAAAA,CACCC,MAAO1X,EAAK0X,MACZC,OAAQ3X,EAAK2X,OACbC,QAAS,OAAO5X,EAAK0X,SAAS1X,EAAK2X,SACnCE,KAAK,MACLC,aAAW,sBAQjB,MAAMC,EA/Ce,aADDC,EAgDOd,GA/CfvP,KAAqBqQ,EAAKC,YAAcD,EAAKC,YAAYC,OADvE,IAAsBF,EAiDpB,MAAMG,EAAYJ,EAAMG,OAClBjY,EAAKkY,EAAUhN,IAAKiN,GAAMA,EAAE,IAC5BC,EAAKF,EAAUhN,IAAKiN,GAAMA,EAAE,IAC5BE,GAAMjX,KAAK8S,OAAOlU,GAAMoB,KAAKkX,OAAOtY,IAAO,EAC3CuY,GAAMnX,KAAK8S,OAAOkE,GAAMhX,KAAKkX,OAAOF,IAAO,EAC3CI,EAAW,EAAYpX,KAAKqX,GAAM,IAElCC,EAAaZ,EAAM5M,IAAKyN,GAASA,EAAKzN,IAAK,I,IAAC0N,EAAGC,GAAE,E,OA/CzD,SACED,EACAC,EACAR,EACAE,EACAC,GAEA,MAAMM,EAAKF,EAAIP,EACTU,EAAKF,EAAIN,EACTS,EAAM5X,KAAK4X,KAAKR,GAChBS,EAAM7X,KAAK6X,KAAKT,GACtB,MAAO,CAACM,EAAKE,EAAMD,EAAKE,EAAKH,EAAKG,EAAMF,EAAKC,EAC/C,CAmC8DE,CAAWN,EAAGC,EAAGR,EAAIE,EAAIC,MAI/EW,EAAapZ,EAAK0X,MAAQ,EAC1B2B,EAAcrZ,EAAK2X,OAAS,EAC5B2B,EAASF,EAAa7B,EACtBgC,EAASF,EAAc7B,EACvBgC,EAAQnY,KAAK8S,IAAImF,EAAQC,GAEzBE,EAAS,I,IAACZ,EAAGC,GAAoB,E,MAAuB,CAP/C,EAQJM,EAAa,EAAIP,EAAIW,EARjB,EASJH,EAAc,EAAIP,EAAIU,IAG3BE,EAAcf,EACjBxN,IACEyN,GACCA,EACGzN,IAAI,CAACwO,EAAI3V,KACR,MAAO4V,EAAIC,GAAMJ,EAAME,GACvB,MAAO,GAAS,IAAN3V,EAAU,IAAM,MAAM4V,EAAGE,QAAQ,MAAMD,EAAGC,QAAQ,OAE7DnU,KAAK,KAAO,MAElBA,KAAK,KAEFoT,EAAKxB,EAASH,EACd4B,EAAKxB,EAAUH,EAErB,OACE,UAACI,MAAAA,CACClX,UAAU,gBACVyK,MAAO,CAAE+O,SAAU/Z,EAAK0X,OACxBE,QAAS,OAAO5X,EAAK0X,SAAS1X,EAAK2X,SACnCE,KAAK,MACLC,aAAY,WAAWV,KAAQC,I,WAE/B,SAACtJ,OAAAA,CAAKvI,EAAGkU,EAAaM,KAAK,OAAOC,OAAO,UAAUC,YAAa,KAC/D/C,EAAMhM,IAAI,CAACF,EAAOkP,KACjB,GAAa,MAATlP,EAAe,OAAO,KAC1B,MAAMmP,EAAMD,EAAM/C,EACZiD,EAAMhZ,KAAKiZ,MAAMH,EAAM/C,GAEvBmD,GAAUH,EAAM,IAAOrB,EAAKxB,EAAS,EACrCiD,GAAUH,EAAM,IAAOrB,EAAKxB,EAAU,GACrCoC,EAAIC,GAAMJ,EAAM,CAACc,EAAQC,IAC1BC,EAAQ1B,EAAKS,EACbkB,EAAQ1B,EAAKQ,EACbmB,EAAQ3D,EAAe/L,IAA2B,UACxD,OACE,SAAC2P,OAAAA,CAECC,YAAW5P,EACX4N,EAAGe,EAAKa,EAAQ,EAChB3B,EAAGe,EAAKa,EAAQ,EAChBhD,MAAO+C,EACP9C,OAAQ+C,EACRV,KAAMW,EACNG,YAAa,KAPRX,OAajB,C,kCChLA,MAAMY,GAAgBxb,EAAAA,EAAAA,GACpB,saACA,CACEC,SAAU,CACRC,QAAS,CACPC,QAAS,8DACTG,UAAW,oEACXF,YACE,yJACFC,QACE,uFACFE,MAAO,yDACPC,KAAM,yDAGVM,gBAAiB,CACfZ,QAAS,aAKf,SAASub,EAAM,G,IAAA,UACbza,EAAS,QACTd,EAAU,UAAS,QACnBe,GAAU,KACPC,GAJU,EAMb,MAAMC,EAAOF,EAAUG,EAAAA,GAAY,OAEnC,OACE,SAACD,EAAAA,CACCE,YAAU,QACVC,eAAcpB,EACdc,WAAWQ,EAAAA,EAAAA,IAAGga,EAAc,CAAEtb,YAAYc,MACtCE,GAGV,CCvCA,SAASwa,EAAM,G,IAAA,UAAE1a,KAAcE,GAAhB,EACb,OACE,SAACya,MAAAA,CAAIta,YAAU,kBAAkBL,UAAU,kC,UACzC,SAAC4a,QAAAA,CACCva,YAAU,QACVL,WAAWQ,EAAAA,EAAAA,IAAG,gCAAiCR,MAC3CE,KAIZ,CAEA,SAAS2a,EAAY,G,IAAA,UAAE7a,KAAcE,GAAhB,EACnB,OAAO,SAAC4a,QAAAA,CAAMza,YAAU,eAAeL,WAAWQ,EAAAA,EAAAA,IAAG,kBAAmBR,MAAgBE,GAC1F,CAEA,SAAS6a,EAAU,G,IAAA,UAAE/a,KAAcE,GAAhB,EACjB,OACE,SAAC8a,QAAAA,CACC3a,YAAU,aACVL,WAAWQ,EAAAA,EAAAA,IAAG,6BAA8BR,MACxCE,GAGV,CAYA,SAAS+a,EAAS,G,IAAA,UAAEjb,KAAcE,GAAhB,EAChB,OACE,SAACgb,KAAAA,CACC7a,YAAU,YACVL,WAAWQ,EAAAA,EAAAA,IACT,8EACAR,MAEEE,GAGV,CAEA,SAASib,EAAU,G,IAAA,UAAEnb,KAAcE,GAAhB,EACjB,OACE,SAACkb,KAAAA,CACC/a,YAAU,aACVL,WAAWQ,EAAAA,EAAAA,IACT,qJACAR,MAEEE,GAGV,CAEA,SAASmb,EAAU,G,IAAA,UAAErb,KAAcE,GAAhB,EACjB,OACE,SAACob,KAAAA,CACCjb,YAAU,aACVL,WAAWQ,EAAAA,EAAAA,IACT,yGACAR,MAEEE,GAGV,C,aC9BA,SAASqb,EAAa7Q,EAAkC8Q,GACtD,OAAa,MAAT9Q,EAAsB,KACnBjK,EAAAA,EAAAA,GAAMiK,EAAO8Q,EACtB,CASO,MAEMC,EACX,kJAEF,SAASC,EAAchR,GACrB,OAAa,MAATA,EAAsB,IACnB,IAAGjK,EAAAA,EAAAA,GAAc,IAARiK,EAAa,KAC/B,CAQO,SAASiR,EAAqB,G,IAAA,MACnCC,EAAK,YACLC,EAAW,UACX7b,GAHmC,EASnC,GAAa,MAAT4b,EAAe,OAAO,KAC1B,MAAM/Z,EAAQga,EACV,IAAI1Q,KAAK,GAAG0Q,cAAwBC,mBAAmB,SACvD,KAGJ,OACE,UAACC,OAAAA,CACCC,cAAY,cACZhc,WAAWQ,EAAAA,EAAAA,IAAG,4CAA6CR,GAC3Dic,MACEpa,EACI,oCAAoCA,kDACpC+J,E,WAGLnL,EAAAA,EAAAA,GAAMmb,EAAO,EAAG,CAAEhb,WAAW,IAC7BiB,GAAS,UAAUA,MAG1B,CAEO,SAASqa,GAAiB,G,IAAA,KAAEpF,EAAI,aAAEqF,EAAY,SAAEC,GAAtB,EAC/B,MAAOC,EAASC,IAActe,EAAAA,EAAAA,UAAkB,wBACzCue,EAASC,IAAcxe,EAAAA,EAAAA,UAAkB,QAE1Cye,GAAa5U,EAAAA,EAAAA,SAAQ,KACzB,MAAM6U,EAAkB,QAAZH,EAAoB,GAAK,EACrC,MAAO,IAAIzF,GAAM9T,KAAK,CAACP,EAAGC,KACxB,GAAgB,SAAZ2Z,EACF,OAAO5Z,EAAEka,KAAKha,cAAcD,EAAEia,MAAQD,EAExC,GAAgB,gBAAZL,EACF,OAAQ5Z,EAAEma,SAASC,aAAe,IAAIla,cAAcD,EAAEka,SAASC,aAAe,IAAMH,EAEtF,MAAMI,EAAKra,EAAEma,UAAUP,GACjBU,EAAKra,EAAEka,UAAUP,GACvB,OAAU,MAANS,GAAoB,MAANC,EAAmB,EAC3B,MAAND,EAAmB,EACb,MAANC,GAAoB,GAChBD,EAAKC,GAAML,KAEpB,CAAC5F,EAAMuF,EAASE,IAEbS,EAAcpV,IACdyU,IAAYzU,EACd4U,EAAYvX,GAAa,QAANA,EAAc,OAAS,QAE1CqX,EAAW1U,GACX4U,EAAmB,SAAR5U,EAAiB,MAAQ,UAIxC,OACE,SAAC+S,MAAAA,CAAI3a,UAAU,oB,UACb,UAAC0a,EAAKA,C,WACJ,SAACG,EAAWA,C,UACV,UAACI,EAAQA,C,WACP,SAACgC,GAAAA,CACCC,MAAM,MACNC,OAAO,OACPd,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,UACNC,OAAO,cACPd,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,eACNC,OAAO,sBACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,gBACNC,OAAO,sBACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,cACNC,OAAO,0BACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,oBACNC,OAAO,0BACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,eACNC,OAAO,eACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,wBACNjB,MA1IZ,oIA2IYkB,OAAO,mBACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAACC,GAAAA,CACCC,MAAM,UACNjB,MAAOR,EACP0B,OAAO,4BACPC,MAAM,QACNf,QAASA,EACTE,QAASA,EACTS,WAAYA,KAEd,SAAC7B,EAASA,CACR1Q,MAAO,CAAE0M,MAAO,KAChBnX,UAAU,8C,SACX,oBAKL,SAAC+a,EAASA,C,SACP0B,EAAW7R,IAAKkP,IACf,MAAMuD,EAAKvD,EAAI8C,QACTU,EAAaxD,EAAI6C,OAASR,EAC1BoB,EAAQF,GAAIG,qBAAuB,KAEnCC,EAAuB,MAATF,EAAgB,KAAOzc,KAAK4c,MAAMH,GACtD,OACE,UAACtC,EAAQA,CAEP0C,gBAAeL,QAAc1R,EAC7B5L,WAAWQ,EAAAA,EAAAA,IACT,mCACA8c,GAAc,qEAEhBM,QAAS,IAAMxB,EAAStC,EAAI6C,M,WAE5B,SAACtB,EAASA,CAACrb,UAAU,wB,SAAyB8Z,EAAI6C,QAClD,SAACkB,GAAAA,CACCC,KAAMT,GAAIR,aAAe,KACzBkB,QAASV,GAAIW,wBAA0B,QAKzC,UAAC3C,EAASA,CAACrb,UAAU,a,WACnB,SAAC+b,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,QACe,MAAfid,GAAuBA,EAAc,EACjC,mBACA,wBACW,MAAfA,GAAuBA,GAAe,KAAO,e,SAG9ClC,EAAagC,EAAO,MAEvB,SAAC5B,EAAAA,CACCC,MAAOyB,GAAIY,0BACXpC,YAAawB,GAAIa,2BAGrB,UAAC7C,EAASA,CAACrb,UAAU,a,WACnB,SAAC+b,OAAAA,CAAK/b,UAAU,sB,SACbub,EAAa8B,GAAIc,oBAAqB,MAEzC,SAACxC,EAAAA,CACCC,MAAOyB,GAAIe,0BACXvC,YAAawB,GAAIa,2BAGrB,SAAC7C,EAASA,CAACrb,UAAU,iC,SAClB0b,EAAc2B,GAAIgB,4BAErB,SAAChD,EAASA,CAACrb,UAAU,iC,SAClB0b,EAAc2B,GAAIiB,4BAErB,SAACjD,EAASA,CAACrb,UAAU,a,UACnB,SAACue,GAAAA,CACCC,GAAInB,GAAIoB,cAAgB,KACxBC,UAAiB,MAANrB,GAAcsB,GAAmBtB,EAAGR,kBAGnD,SAACxB,EAASA,CAACrb,UAAU,iC,SAClBub,EAAa8B,GAAIuB,iBAAkB,MAEtC,SAACvD,EAASA,CAACrb,UAAU,iCAAiCic,MAAOR,E,SAC1DF,EAAa8B,GAAIwB,0BAA2B,MAE/C,SAACxD,EAASA,CAAC5Q,MAAO,CAAE0M,MAAO,K,UACzB,SAACT,EAAUA,CACTC,QAASmD,EAAInD,QACbC,MAAO,GAAKkI,eAAiD,GAC7DjI,KAAM,GACNC,KAAM,GACNC,SAAU+C,EAAI/C,SACdC,OAAQ8C,EAAI9C,OACZC,QAAS6C,EAAI7C,QACbxX,KAAM,CAAE0X,MAAO,IAAKC,OAAQ,UArE3B0C,EAAI6C,cA+EzB,CAYA,SAASM,GAAe,G,IAAA,MACtBC,EAAK,MACLjB,EAAK,OACLkB,EAAM,MACNC,EAAK,QACLf,EAAO,QACPE,EAAO,WACPS,GAPsB,EAStB,MACM+B,EADS1C,IAAYc,EACsB,QAAZZ,EAAoByC,EAAAA,EAAUC,EAAAA,EAA5CC,EAAAA,EACvB,OACE,SAAC/D,EAASA,CACRyC,QAAS,IAAMZ,EAAWG,GAC1BlB,MAAOA,EACPjc,WAAWQ,EAAAA,EAAAA,IACT,yEACU,UAAV4c,GAAqB,c,UAGvB,UAACrB,OAAAA,CAAK/b,UAAU,iC,UACbkd,GACD,SAAC6B,EAAAA,CAAK/e,UAAU,yCAIxB,CAMO,SAASmf,GAAWX,GACzB,OAAO1d,KAAK4c,MAAW,GAALc,GAAW,EAC/B,CAOO,SAASY,GAAcZ,EAAmBE,GAC/C,OAAU,MAANF,EAAmB,uCACnBE,EAAkB,iCAClBS,GAAWX,GAAM,GAAW,gCAC5BW,GAAWX,GAAM,EAAU,gCACxB,8BACT,CAEA,SAASD,GAAQ,G,IAAA,GAAEC,EAAE,UAAEE,GAAN,EACf,OACE,SAAC3C,OAAAA,CACCC,cAAY,eACZhc,WAAWQ,EAAAA,EAAAA,IACT,gEACA4e,GAAcZ,EAAIE,I,SAGb,MAANF,EAAa,KAAM/d,EAAAA,EAAAA,GAAM+d,EAAI,EAAG,CAAE5d,WAAW,KAGpD,CAGO,MAAMye,GAA0B,IAGhC,SAASV,GAAmBb,GACjC,IAAKA,EAAM,OAAO,EAClB,MAAMwB,EAAM,IAAInU,KAAK,GAAG2S,cACxB,OAAQ3S,KAAKoU,MAAQD,EAAIE,WAAa,MAAaH,EACrD,CAYA,MAAMI,GAAuB,IAE7B,SAAS5B,GAAc,G,IAAA,KAAEC,EAAI,QAAEC,GAAR,EACrB,IAAKD,EAAM,OAAO,SAACzC,EAASA,CAACrb,UAAU,wB,SAAwB,MAC/D,MAAMsf,EAAM,IAAInU,KAAK,GAAG2S,cAClBY,EAAYC,GAAmBb,GACrC,OACE,UAACzC,EAASA,CACRrb,UAAU,+CACVic,MAAOyC,EAAY,aAAaW,gBAAkCzT,E,UAEjE0T,EAAIxD,mBAAmB,SACvB4C,IACC,SAACjE,EAAKA,CAACvb,QAAQ,YAAYc,UAAU,kD,SAAkD,cAI7E,MAAX+d,GAAmBA,EAAU0B,KAC5B,UAAC1D,OAAAA,CACCC,cAAY,UACZhc,UAAU,sCACVic,MAAM,mI,WAELxb,EAAAA,EAAAA,GAAgB,IAAVsd,EAAe,GAAG,iBAKnC,C,eCraO,MAAM2B,GAAkD,CAC7DC,SAAU,qBACVC,IAAK,iBACLC,aAAc,4BAsBHC,GAAoB,UAI1B,SAASC,GAAM1C,GACpB,MAAwB,QAAjBA,EAAG2C,SACZ,CAGO,SAASje,GAAesb,GAC7B,MAAwB,iBAAjBA,EAAG2C,SACZ,CCwBA,MAAMC,GAAgC,CACpC,CAAErY,IAAK,cAAesV,MAAO,QAASgD,SAAU,YAAaC,KAAM,QACnE,CAAEvY,IAAK,iBAAkBsV,MAAO,MAAOgD,SAAU,YAAaC,KAAM,QACpE,CAAEvY,IAAK,YAAasV,MAAO,YAAagD,SAAU,gBAAiBC,KAAM,aACzE,CACEvY,IAAK,mBACLsV,MAAO,wBACPgD,SAAU,qDACVC,KAAM,SAER,CACEvY,IAAK,4BACLsV,MAAO,UACPgD,SAAU,kDACVC,KAAM,SAER,CAGEvY,IAAK,yBACLsV,MAAO,YACPgD,SAAU,mDACVC,KAAM,SAER,CACEvY,IAAK,sBACLsV,MAAO,eACPgD,SAAU,mEACVC,KAAM,SACNC,YAAa,6BAEf,CACExY,IAAK,uBACLsV,MAAO,eACPgD,SAAU,sEACVC,KAAM,SACNE,WAAW,GAEb,CACEzY,IAAK,0BACLsV,MAAO,cACPgD,SAAU,iFACVC,KAAM,SAER,CACEvY,IAAK,4BACLsV,MAAO,2BACPgD,SAAU,8DACVC,KAAM,QACNE,WAAW,GAEb,CACEzY,IAAK,sBACLsV,MAAO,gBACPgD,SAAU,gEACVC,KAAM,SACNC,YAAa,6BAEf,CACExY,IAAK,uBACLsV,MAAO,gBACPgD,SAAU,mEACVC,KAAM,SACNE,WAAW,GAEb,CACEzY,IAAK,uBACLsV,MAAO,qBACPgD,SAAU,wEACVC,KAAM,SACNE,WAAW,GAEb,CACEzY,IAAK,0BACLsV,MAAO,oBACPgD,SAAU,mFACVC,KAAM,SAER,CACEvY,IAAK,4BACLsV,MAAO,4BACPgD,SAAU,6EACVC,KAAM,QACNE,WAAW,GAEb,CACEzY,IAAK,uBACLsV,MAAO,iBACPgD,SAAU,0DACVC,KAAM,OACNE,WAAW,IAMf,SAASC,GAAQzG,GACf,MAAiB,cAAbA,EAAIsG,KAA6B,SACpB,SAAbtG,EAAIsG,MAAgC,SAAbtG,EAAIsG,KAAwB,OAChD,OACT,CAYA,SAASI,GAAWzG,EAAoBD,GACtC,MAAMnP,EAAQoP,EAAID,EAAIjS,KACtB,GAAiB,SAAbiS,EAAIsG,KAAiB,OAZ3B,SAAoB3b,GAClB,IAAKA,EAAK,MAAO,IACjB,IACE,OAAO,IAAI2G,KAAK,GAAG3G,cAAgBsX,mBAAmB,QACxD,CAAE,MACA,OAAOtX,CACT,CACF,CAKkCgc,CAAW9V,GAC3C,GAAa,MAATA,GAA2B,KAAVA,EAAc,MAAO,IAC1C,GAAqB,iBAAVA,EAAoB,OAAO+V,OAAO/V,GAC7C,OAAQmP,EAAIsG,MACV,IAAK,QAGH,OAAO1f,EAAAA,EAAAA,GAAMiK,EAAO,GACtB,IAAK,QACH,MAAO,IAAGjK,EAAAA,EAAAA,GAAc,IAARiK,EAAa,MAC/B,IAAK,QACH,OAAOjK,EAAAA,EAAAA,GAAMiK,EAAO,EAAG,CAAE9J,WAAW,IACtC,QACE,OAAOH,EAAAA,EAAAA,GAAMiK,EAAO,GAE1B,CAqDA,MAAMgW,GAAiE,CACrE,CAAEhW,MAAO,OAAQwS,MAAO,QACxB,CAAExS,MAAO,WAAYwS,MAAO,aAC5B,CAAExS,MAAO,MAAOwS,MAAO,mBAGlB,SAASyD,GAAc,G,IAAA,SAAEC,EAAQ,SAAEC,GAAZ,EAC5B,MAAOxE,EAASC,IAActe,EAAAA,EAAAA,UAAoB,gBAC3Cue,EAASC,IAAcxe,EAAAA,EAAAA,UAAkB,SACzC8iB,EAAiBC,IAAsB/iB,EAAAA,EAAAA,UAA0B,QAElE8Y,GAAOjP,EAAAA,EAAAA,SAAQ,KACnB,MAAMmZ,EAAS,IAAInd,IAAIgd,GAOvB,MAAO,IANUD,EAAS3e,OACvBgD,GACC+b,EAAOld,IAAImB,EAAEgc,iBACbhc,EAAE4X,cACmB,SAApBiE,GAA8B7b,EAAE+a,YAAcc,KAE9B9d,KAAK,CAACP,EAAGC,IAtElC,SAAuBD,EAAkBC,EAAkBkF,EAAgB8U,GACzE,MAAMI,EAAKra,EAAEmF,GACPmV,EAAKra,EAAEkF,GACPsZ,EAAc,QAARxE,EAAgB,GAAK,EAGjC,OAAU,MAANI,GAAoB,MAANC,EAAmB,EAC3B,MAAND,EAAmB,EACb,MAANC,GAAoB,EAEN,iBAAPD,GAAiC,iBAAPC,GAAyBD,EAAKC,GAAMmE,EACvD,kBAAPpE,GAAkC,kBAAPC,GAC5BrY,OAAOoY,GAAMpY,OAAOqY,IAAOmE,EAE9BT,OAAO3D,GAAIna,cAAc8d,OAAO1D,IAAOmE,CAChD,CAuDwCC,CAAc1e,EAAGC,EAAG2Z,EAASE,KAChE,CAACqE,EAAUC,EAAUxE,EAASE,EAASuE,IAIpCM,GAAiBvZ,EAAAA,EAAAA,SAAQ,KAC7B,MAAMmZ,EAAS,IAAInd,IAAIgd,GACjBQ,EAAkC,CAAC,EACzC,IAAK,MAAMpc,KAAK2b,EACTI,EAAOld,IAAImB,EAAEgc,iBAAoBhc,EAAE4X,cACxCwE,EAAQpc,EAAE+a,YAAcqB,EAAQpc,EAAE+a,YAAc,GAAK,GAEvD,OAAOqB,GACN,CAACT,EAAUC,IAYRS,EAAiBrB,GAAQhe,OAC5Bsf,IAAOA,EAAElB,YAAwB,mBAAVkB,EAAE3Z,KAA4BiZ,EAAS1c,OAAS,IASpEqd,GACJ,UAAC7G,MAAAA,CAAI3a,UAAU,oD,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAgC,gBAChD,SAACyhB,GAAAA,EAAWA,CACVra,KAAK,SACLsD,MAAOoW,EACPY,cAAgBvf,IACVA,GAAG4e,EAAmB5e,IAE5B1C,KAAK,K,SAEJihB,GAAkB9V,IAAK,I,IAAA,MAAEF,EAAK,MAAEwS,GAAO,E,OACtC,UAACyE,GAAAA,EAAeA,CAAajX,MAAOA,E,UACjCwS,EACU,SAAVxS,IACC,UAACqR,OAAAA,CAAK/b,UAAU,6B,UAA6B,IAAEohB,EAAe1W,IAAU,EAAE,SAHxDA,WAS5B,UAAC3K,EAAAA,EAAMA,CAACb,QAAQ,UAAUO,KAAK,KAAKme,QA5BnB,MAhEvB,SAA4BgE,EAAaC,GAEvC,MAAMC,EAAO,IAAIC,KAAK,CAAC,SAAKH,GAAM,CAAExa,KAAM,2BACpChE,EAAM4e,IAAIC,gBAAgBH,GAC1BI,EAASla,SAASma,cAAc,KACtCD,EAAOE,KAAOhf,EACd8e,EAAOG,SAAWR,EAClB7Z,SAASc,KAAKwZ,YAAYJ,GAC1BA,EAAOK,QACPL,EAAOM,SACPR,IAAIS,gBAAgBrf,EACtB,CAwDIsf,CAzEG,SAAe5L,GAGpB,MAAO,GAFQmJ,GAAQrV,IAAK2W,GAAMA,EAAErB,UAAU9a,KAAK,SACtC0R,EAAKlM,IAAKkP,GAAQmG,GAAQrV,IAAKiP,GAd9C,SAAiBC,EAAoBD,GACnC,MAAMnP,EAAQoP,EAAID,EAAIjS,KACtB,OAAa,MAAT8C,EAAsB,GACT,cAAbmP,EAAIsG,KAA6BT,GAAiB5F,EAAIkG,WACrC,iBAAVtV,GAEkB,UAAbmP,EAAIsG,KAA2B,IAARzV,EAAcA,GACtC1J,eAAe,QAAS,CAAEE,sBAAuB,IAEzDuf,OAAO/V,GAAOsD,QAAQ,KAAM,IACrC,CAIsD2U,CAAQ7I,EAAKD,IAAMzU,KAAK,MAAMA,KAAK,SAEzF,CAmEgBwd,CAAM9L,GAEM,eADV,IAAI3L,MAAOC,cAAc/I,MAAM,EAAG,YA0BawgB,SAA0B,IAAhB/L,EAAK3S,O,WACxE,SAAC2e,EAAAA,EAAQA,CAAC9iB,UAAU,iBAAiB,mBAM3C,OAAoB,IAAhB8W,EAAK3S,QAEL,UAACwW,MAAAA,CAAI3a,UAAU,Y,UACZwhB,GACD,SAAC7G,MAAAA,CAAI3a,UAAU,wF,SACQ,SAApB8gB,EACG,+CACA,oEAOV,UAACnG,MAAAA,CAAI3a,UAAU,Y,UACZwhB,GACD,SAAC7G,MAAAA,CAAI3a,UAAU,oB,UACb,UAAC0a,EAAKA,C,WACJ,SAACG,EAAWA,C,UACV,SAACI,EAAQA,C,SACNqG,EAAe1W,IAAKiP,IACnB,SAACsB,EAASA,CAERc,MAAOpC,EAAIqG,SACXlgB,WAAWQ,EAAAA,EAAAA,IACT,6BACiB,UAAjB8f,GAAQzG,IAAoB,aACX,WAAjByG,GAAQzG,IAAqB,eAE/B+D,QAAS,KAAMZ,OA9EXpV,EA8EsBiS,EAAIjS,SA7ExCyU,IAAYzU,EACd4U,EAAYvX,GAAa,QAANA,EAAc,OAAS,QAE1CqX,EAAW1U,GACX4U,EAAmB,gBAAR5U,EAAwB,OAAS,SAL7B,IAACA,G,UAgFJ,UAACmU,OAAAA,CAAK/b,UAAU,iC,UACb6Z,EAAIqD,OACL,SAAC6F,GAAAA,CAAcC,OAAQ3G,IAAYxC,EAAIjS,IAAK8U,IAAKH,QAX9C1C,EAAIjS,WAiBjB,SAACmT,EAASA,C,SACPjE,EAAKlM,IAAKkP,IACT,SAACmB,EAAQA,C,SACNqG,EAAe1W,IAAKiP,GACN,cAAbA,EAAIsG,MACF,SAAC9E,EAASA,CAAerb,UAAU,c,SAChC+f,GAAMjG,IACL,SAACW,EAAKA,CACJvb,QAAQ,YACRc,UAAU,UACVyK,MAAO,CAAE2P,MAAO0F,I,SDtWnC,SC2WiB,SAAC/D,OAAAA,CAAK/b,UAAU,gC,SD5W5B,eCkWwB6Z,EAAIjS,MAgBpB,UAACyT,EAASA,CAERrb,WAAWQ,EAAAA,EAAAA,IACI,SAAbqZ,EAAIsG,MAAmB,cACN,UAAjBG,GAAQzG,IAAoB,2B,UAG7B0G,GAAWzG,EAAKD,GAChBA,EAAIuG,cACH,SAACzE,EAAoBA,CACnBC,MAAO9B,EAAID,EAAIuG,aACfvE,YAAa/B,EAAIoE,yBAVhBrE,EAAIjS,OApBFkS,EAAI3W,cA2CjC,CAEA,SAAS4f,GAAc,G,IAAA,OAAEC,EAAM,IAAEtG,GAAV,EACrB,OAAKsG,EACU,QAARtG,GAAgB,SAACsC,EAAAA,EAAOA,CAAChf,UAAU,aAAe,SAACif,EAAAA,EAASA,CAACjf,UAAU,aAD1D,SAACkf,EAAAA,EAAWA,CAAClf,UAAU,oCAE7C,C,iHCnZO,MAgPMijB,GAAyDvX,IAC1DA,GAASoD,QAAUA,GAAQrG,IAAqD,CACpFgF,gBAAiB,CACbnB,MAAO,CACH/B,SAAS,EACTE,MAAO,SAGfuF,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,oBACFsI,IAwGEwX,GAAqExX,IACtEA,EAAQoD,QAAUA,GAAQqF,KAAkE,CAChGnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,qCACFsI,ICzXX,MAAMyX,GAAoD,CACxD,CAAEzY,MAAO,MAAOwS,MAAO,OACvB,CAAExS,MAAO,OAAQwS,MAAO,QACxB,CAAExS,MAAO,QAASwS,MAAO,UAGrBkG,GAAQ,UAGd,SAASC,GAAaC,EAAeC,GACnC,MAAM1hB,EAAQ,IAAIsJ,KAAK,GAAGmY,cAC1B,GAAgB,UAAZC,EACF,OAAO1hB,EAAMia,mBAAmB,QAAS,CAAE0H,MAAO,QAASC,KAAM,YAEnE,MAAMC,EAAW7hB,EAAMia,mBAAmB,QAAS,CAAE6H,IAAK,UAAWH,MAAO,YAC5E,MAAmB,SAAZD,EAAqB,MAAMG,IAAaA,CACjD,CAyCO,SAASE,GAASC,GACvB,MAAMC,EAAOD,GAAM,IAAS,IAAOA,GAAM,IAAO,IAAM,GACtD,OAAQ/iB,KAAK4c,MAAMmG,EAAKC,GAAQA,GAAM9iB,eAAe,QACvD,CASO,SAAS+iB,GAAsB,G,IAAA,KAAEpH,GAAF,EACpC,MAAO4G,EAASS,IAAchmB,EAAAA,EAAAA,UAAsB,UAC9C,KAAE8R,EAAI,MAAEmU,EAAK,KAAEC,GC3EhB,SAA+BvH,EAAqB4G,GACzD,MAAOzT,EAAMqU,IAAWnmB,EAAAA,EAAAA,UAAsC,OACvDimB,EAAOG,IAAYpmB,EAAAA,EAAAA,WAAS,IAC5BkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,WAAS,GAgCjC,OA9BAC,EAAAA,EAAAA,WAAU,KACR,IAAK0e,EAIH,OAHAwH,EAAQ,MACRC,GAAS,QACTC,GAAQ,GAGV,IAAIC,GAAa,EFiC8B,IAAuC5Y,EEftF,OAjBA0Y,GAAS,GACTC,GAAQ,IF+B8E3Y,EE7BlD,CAAEsB,MAAO,CAAEuX,IAAK5H,EAAM4G,aF8BlD7X,GAASoD,QAAUA,GAAQrG,IAAyE,CACxGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,qCACFsI,KEvCJhN,KAAM,I,IAAEoR,KAAM0U,GAAU,EACnBF,GACJH,EAAQK,GAAY,QAErBC,MAAM,KACDH,IACJH,EAAQ,MACRE,GAAQ,MAETK,QAAQ,KACFJ,GAAYF,GAAS,KAGvB,KACLE,GAAa,IAEd,CAAC3H,EAAM4G,IAEH,CAAEzT,OAAMmU,QAAOC,OACxB,CDuCgCS,CAAsBhI,EAAM4G,IAClDzT,KAAM8U,GE5ET,SAA8BjI,GACnC,MAAO7M,EAAMqU,IAAWnmB,EAAAA,EAAAA,UAA2C,OAC5DkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,WAAS,GAqBjC,OAnBAC,EAAAA,EAAAA,WAAU,KAGR,GAFAkmB,EAAQ,MACRE,GAAQ,IACH1H,EAAM,OACX,IAAI2H,GAAa,EHuD6B,IAAuC5Y,EG7CrF,OH6CqFA,EGrDlD,CAAEsB,MAAO,CAAEuX,IAAK5H,KHsD3CjR,EAAQoD,QAAUA,GAAQrG,IAAwE,CACtGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,oCACFsI,KG/DJhN,KAAM,I,IAAEoR,KAAM0U,GAAU,EAClBF,GAAYH,EAAQK,GAAY,QAEtCC,MAAM,KACAH,GAAYD,GAAQ,KAGtB,KACLC,GAAa,IAEd,CAAC3H,IAEG,CAAE7M,OAAMoU,OACjB,CFoDgCW,CAAqBlI,GAE7CmI,EAAShV,GAAMgV,QAAU,GACzBC,EAxCD,SACLD,EACAvB,EACAqB,GAEA,MAAM3hB,EAAe6hB,EAAOla,IAAKoa,IAAU,CACzC9H,MAAOmG,GAAa2B,EAAK1B,MAAOC,GAChC0B,OAAQD,EAAKE,UACbC,OAAQH,EAAKG,UAEThjB,EAAIyiB,GAAaA,YAQvB,MAPgB,UAAZrB,GAAuBphB,GACzBc,EAAIM,KAAK,CACP2Z,MAAO,GAAGmG,GAAalhB,EAAEijB,MAAO,iBAChCvgB,SAAU1C,EAAEkjB,WACZC,KAAM,CAACnjB,EAAEkjB,WAAaljB,EAAEojB,QAASpjB,EAAEqjB,QAAUrjB,EAAEkjB,cAG5CpiB,CACT,CAqBoBwiB,CAAOX,EAAQvB,EAASqB,GAE1C,OACE,UAACjK,MAAAA,CAAI3a,UAAU,Y,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAgC,UAChD,SAACyhB,GAAAA,EAAWA,CACVra,KAAK,SACLsD,MAAO6Y,EACP7B,cAAgBvf,IACVA,GAAG6hB,EAAW7hB,IAEpB1C,KAAK,K,SAEJ0jB,GAASvY,IAAKiN,IACb,SAAC8J,GAAAA,EAAeA,CAAejX,MAAOmN,EAAEnN,M,SACrCmN,EAAEqF,OADiBrF,EAAEnN,aAO7BwZ,GACC,SAACwB,GAAAA,C,SAAQ,iDACPzB,GAA2B,IAAlBa,EAAO3gB,QAClB,SAACuhB,GAAAA,C,SAAQ,uBACW,IAAlBZ,EAAO3gB,QACT,SAACuhB,GAAAA,C,SAAQ,oJAKT,sB,WACE,SAAC/K,MAAAA,CAAI3a,UAAU,O,UACb,SAAC2lB,GAAAA,EAAmBA,CAACxO,MAAM,OAAOC,OAAO,O,UACvC,UAACwO,GAAAA,EAAQA,CAAC9V,KAAMiV,E,WACd,SAACc,GAAAA,EAAaA,CAACC,gBAAgB,SAC/B,SAACC,GAAAA,EAAKA,CAACC,QAAQ,QAAQC,SAAU,MACjC,SAACC,GAAAA,EAAKA,CACJD,SAAU,GACV/I,MAAO,CACLxS,MAAO,KACPyb,OAAQ,GACRC,SAAU,aACV3b,MAAO,CAAEwb,SAAU,QAGvB,SAACI,GAAAA,EAAOA,CACNC,UAAW,CAACnkB,EAAGokB,IACJ,WAATA,EACI,GAAGzlB,KAAK4c,MAAMhZ,OAAOvC,IAAInB,eAAe,cAC/B,aAATulB,EACE,CAAC,YAAY3C,GAASlf,OAAOvC,SAAU,YACvCse,OAAOte,MAKjB,SAACqkB,GAAAA,EAAGA,CAACR,QAAQ,SAASS,QAAQ,KAAKhN,KAAM2J,GAAOsD,mBAAmB,KACnE,SAACF,GAAAA,EAAGA,CACFR,QAAQ,WACRS,QAAQ,KACRhN,KAAM2J,GACN7I,YAAa,IACbb,OAAQ0J,GACR0C,gBAAgB,MAChBY,mBAAmB,E,UAEnB,SAACC,GAAAA,EAAQA,CAACX,QAAQ,OAAO7O,MAAO,EAAGuC,OAAQ0J,GAAOwD,UAAU,gBAKpE,SAACC,GAAAA,CACC3J,MAAOpN,GAAMgX,iBAAmB,GAChCC,OAAQjX,GAAMkX,WAAa,EAC3B7B,OAAQrV,GAAMqV,QAAU,EACxB8B,aAAcnX,GAAMoX,eAAiB,IAE1B,UAAZ3D,GAAuBqB,IAAe,SAACuC,GAAAA,CAAkBrX,KAAM8U,SAK1E,CAEA,SAASc,GAAQ,G,IAAA,SAAEpQ,GAAF,EACf,OACE,SAACqF,MAAAA,CAAI3a,UAAU,uF,SACZsV,GAGP,CAOA,SAASuR,GAAY,G,IAAA,MACnB3J,EAAK,OACL6J,EAAM,OACN5B,EAAM,aACN8B,GAJmB,EAWnB,OACE,UAACtM,MAAAA,CAAI3a,UAAU,4E,WACb,UAAC+b,OAAAA,C,UAAK,WACKmB,EAAM,KAAGpc,KAAK4c,MAAMqJ,GAAQ/lB,eAAe,SAAS,YAAUmkB,EAAQ,IACnE,IAAXA,EAAe,OAAS,SAAS,OAEnC8B,EAAe,IACd,UAAClL,OAAAA,C,UACEkL,EAAa,IAAmB,IAAjBA,EAAqB,aAAe,gBAAgB,SAAO/J,EAAM,MAC/D,IAAjB+J,EAAqB,QAAU,UAAU,kBAKpD,CAUO,SAASE,GAAkB,G,IAAA,KAAErX,GAAF,EAChC,MAAM3N,EAAI2N,EAAK8U,YACf,OAAKziB,GAMH,SAACwY,MAAAA,CAAI3a,UAAU,8E,UACb,UAAC6X,IAAAA,C,WACC,UAACkE,OAAAA,CAAK/b,UAAU,8B,UAA8B,gBAlBnCwE,EAmBcrC,EAAEijB,MAlB1B,IAAIja,KAAK,GAAG3G,cAAgBsX,mBAAmB,QAAS,CAC7D0H,MAAO,OACPC,KAAM,aAgBgC,cAAYG,GAASzhB,EAAEkjB,YAAY,SAC9D,oBACWzB,GAASzhB,EAAEojB,SAAS,OAAK3B,GAASzhB,EAAEqjB,SAAS,gFAE9D1V,EAAKsX,SACF,2BAA2B,IAAIjc,KAAK,GAAG2E,EAAKsX,qBAAqBtL,mBAAmB,WACpF,GAAG,SAdJhM,EAAKuX,OACV,UAACxP,IAAAA,CAAE7X,UAAU,gC,UAAgC,qBAAmB8P,EAAKuX,SACnE,KAbR,IAAmB7iB,CA8BnB,CG3NO,SAAS8iB,GAAY,G,IAAA,OAC1BC,EAAM,OACNC,EAAM,QACNC,EAAO,KACPhO,EAAO,UAAS,UAChBiO,GAAY,GALc,EAO1B,MAAMC,EAAWC,GAIXC,GAnCqB,GAqCrBC,EAAKP,EACLQ,EAAKP,EAELviB,EAAI,CACR,KAAK6iB,EAAGL,EAAUE,MAAaI,EAxCF,KAyC7B,KAAKD,EAAGL,EAAUE,MAAaI,EARf,KAShB,KAAKD,EAAGL,EAAUE,MAAaI,EAAGF,OAAUC,EAAGL,EAX9BO,OAWuDD,EAAGF,KAC3E,KAAKC,EAAGL,EAZSO,OAYgBD,EAAGF,KACpC,KAAKC,EAAGL,EAAUE,MAAaI,EAAGF,OAAUC,EAAGL,EAAUE,MAAaI,EAXtD,KAYhB,KAAKD,EAAGL,EAAUE,MAAaI,EA7CF,KA8C7B,KACA3iB,KAAK,KAEP,OACE,UAAC6iB,IAAAA,C,WACC,SAACza,OAAAA,CAAKvI,EAAGA,EAAGwU,KAAMA,EAAMc,YAAa,GAAKb,OAAO,OAAOC,YAAa,OACrE,SAAC1G,OAAAA,CACCiV,GAAIJ,EAAGL,EAAUE,EAAW,GAC5BQ,GAAIL,EAAGL,EAAUE,EAAW,GAC5BS,GAAIL,EAtBQ,GAuBZM,GAAIN,EAvBQ,GAwBZrO,OAAO,UACPC,YAAa,EACbmM,gBAAgB,QAEjB4B,IACC,UAACY,OAAAA,CACChQ,EAAGwP,EAAGL,GACNlP,EAAGwP,EAhEoB,GAgET,EACdQ,WAAW,SACXtC,SAAU,GACVxM,KAAK,O,UACN,YAtEmB,GAuEK,OAtEJ,GAsEuB,UAKpD,CC9EO,MAAM+O,GAAmB,CAC9BC,IAAK,UACLC,IAAK,UACLC,IAAK,WASMC,GAAmB,CAC9BC,IAAK,MACLJ,IAAK,MACLC,IAAK,MACLC,IAAK,QAIMG,GAAY,UCClB,SAASC,GAAgB,G,IAAA,QAAEC,EAAO,gBAAEC,EAAe,MAAE9R,EAAK,OAAEC,GAAnC,EAC9B,GAAuB,IAAnB4R,EAAQ7kB,OACV,OACE,SAACwW,MAAAA,CAAI3a,UAAU,wE,SAAwE,wCAM3F,MAAMkpB,EAAiD,GACjDC,EAAShS,EAAQ+R,EADU,GAE3BE,EAAShS,EAFO,GAAuB,GAIvC1X,EAAKspB,EAAQpe,IAAKvB,GAAMA,EAAEggB,YAC1BC,EAAOxoB,KAAK8S,OAAOlU,GACnB6pB,EAAOzoB,KAAKkX,OAAOtY,GAEnB8pB,EAASR,EAAQpe,IAAKvB,GAAMA,EAAEogB,SAASxnB,OAAQgD,GAAwB,MAALA,GAClEykB,EAAY,CAACT,EAAgBR,IAAKQ,EAAgBN,KAAK1mB,OAC1DE,GAAwB,MAALA,GAEhBwnB,EAAOH,EAAOrlB,OAAS,EAAIrD,KAAK8S,OAAO4V,GAAU,GAAK,GAEtDI,EAAU9oB,KAAK8S,IAAI+V,KAASD,GAAY,GAExCnC,EAAUjP,GAAc4Q,GAAgB5Q,EAAIgR,GAAQxoB,KAAKkX,IAAI,KAAMuR,EAAOD,GAASH,EACnF3B,EAAUjP,GAjBM,GAkBP6Q,GAAW7Q,EAAIqR,GAAW9oB,KAAKkX,IAAI,KALrC6R,GAKkDD,GAAYR,EAGrEU,EACJd,EACGpe,IAAI,CAACvB,EAAG5F,KACP,GAAiB,MAAb4F,EAAEogB,QAAiB,OAAO,KAC9B,MAAMpQ,EAAKkO,EAAOle,EAAEggB,YACd/P,EAAKkO,EAAOne,EAAEogB,SACpB,MAAO,GAAS,IAANhmB,EAAU,IAAM,MAAM4V,EAAGE,QAAQ,MAAMD,EAAGC,QAAQ,OAE7DtX,OAAO8nB,SACP3kB,KAAK,KACR,KAAKmiB,EAAOgC,GAAMhQ,QAAQ,MAAMiO,EAAOoC,GAASrQ,QAAQ,KACxD,KAAKgO,EAAO+B,GAAM/P,QAAQ,MAAMiO,EAAOoC,GAASrQ,QAAQ,OAEpDxB,GAAMuR,EAAOC,GAAQ,EAE3B,OACE,UAACrS,MAAAA,CACClX,UAAU,gBACVqX,QAAS,OAAOF,KAASC,IACzBE,KAAK,MACLC,aAAW,wC,WAEX,UAACyS,OAAAA,C,WACC,UAACC,iBAAAA,CAAe9mB,GAAG,uBAAuB+kB,GAAG,IAAIE,GAAG,IAAID,GAAG,IAAIE,GAAG,I,WAChE,SAAC6B,OAAAA,CAAKC,OAAO,KAAKC,UAAU,UAAUC,YAAa,OACnD,SAACH,OAAAA,CAAKC,OAAO,OAAOC,UAAU,UAAUC,YAAa,SAEvD,UAACJ,iBAAAA,CAAe9mB,GAAG,sBAAsB+kB,GAAG,IAAIE,GAAG,IAAID,GAAG,IAAIE,GAAG,I,WAC/D,SAAC6B,OAAAA,CAAKC,OAAO,KAAKC,UAAU,UAAUC,YAAa,OACnD,SAACH,OAAAA,CAAKC,OAAO,OAAOC,UAAU,UAAUC,YAAa,aAIzD,SAAChQ,OAAAA,CACC/B,EAAG4Q,EACH3Q,EAAGiP,EA3CIqC,IA4CP1S,MAAOgS,EACP/R,OAAQoQ,EAAO,GAAKA,EA7CbqC,IA8CPpQ,KAAK,gCAGP,SAACjM,OAAAA,CAAKvI,EAAG6kB,EAAYrQ,KAAK,4BAA4BC,OAAO,UAAUC,YAAa,MAElF,CAAC,MAAO,OAAiB/O,IAAKhD,IAC9B,MAAM0iB,EAAQrB,EAAgBrhB,GAC9B,OAAa,MAAT0iB,EAAsB,MAExB,SAACrC,IAAAA,C,UACC,SAAChV,OAAAA,CACCsX,gBAAe3iB,EACfsgB,GAAIgB,EACJf,GAAIe,EAAcC,EAClBf,GAAIZ,EAAO8C,GACXjC,GAAIb,EAAO8C,GACX5Q,OAAQ8O,GAAiB5gB,GACzB+R,YAAa,IACbmM,gBAAgB,SATZle,MAgBZ,SAAC0gB,OAAAA,CAAKhQ,EAAG4Q,EAAcC,EAAQ5Q,EAAGnB,EAAS,EAAGmR,WAAW,MAAMtC,SAAU,G,SACtE,CAAE,MAAO,OAAiBrb,IAAKhD,IAC9B,MAAM0iB,EAAQrB,EAAgBrhB,GAC9B,OAAa,MAAT0iB,EAAsB,MAExB,UAACE,QAAAA,CAAgBhS,GAAI,EAAGiB,KAAM+O,GAAiB5gB,G,UAC5CghB,GAAiBhhB,GAAK,IAAE0iB,EAAM/Q,QAAQ,GAAG,OADhC3R,QAOlB,SAACqL,OAAAA,CACCiV,GAAIgB,EACJf,GAAIe,EAAcC,EAClBf,GAAIZ,EAAO,GACXa,GAAIb,EAAO,GACX9N,OAAO,UACPC,YAAa,EACbmM,gBAAgB,SAGlB,SAACwB,GAAWA,CAACC,OAAQA,EAAQC,OAAQA,EAAQC,QAAS1P,MAG5D,CC5IO,SAAS0S,GAAgBjgB,EAAckgB,EAAmBC,GAC/D,IACE,IACG,CAAC,YAAa,aAAa1d,SAAS2d,OAAOC,SAASC,WACV,MAA3CC,aAAaC,QAAQ,kBAErB,OAAOL,EACT,MAAMjgB,EAAQhG,OAAOqmB,aAAaC,QAAQxgB,IAC1C,OAAOkgB,EAAQzd,SAASvC,GAASA,EAAQigB,CAC3C,CAAE,MACA,OAAOA,CACT,CACF,CAIO,MAAMM,GAAyBR,GAAgB,oBAAqB,CAAC,EAAG,EAAG,GAAI,GCMhFS,GAAaT,GAAgB,kBAAmB,CAAC,IAAM,KAAQ,KAC/DU,GAAgC,GAChCC,GAA6B,GAC7BC,GAAW,IAAIxnB,IACrB,IAAIynB,GAAY,EACZC,GAAa,EAEjB,MAAMC,GAAU,IAAI1kB,IACpB,IAAI2kB,GAAY,EAUT,MAAMC,GARb,WACE,IACE,MAA+B,oBAAjBX,cAA2E,MAA3CA,aAAaC,QAAQ,iBACrE,CAAE,MACA,OAAO,CACT,CACF,CAEiCW,GAC3BC,GAAeF,GAErB,SAASG,GAAOtY,GAEd,MAAMlB,EAAS,sCAAsClR,KAAKoS,EAAMgT,MAC5D6E,GACAD,GACA9Y,EAAOlO,SAAW+mB,KACpB7Y,EAAOyZ,QACPP,MAEFlZ,EAAO9O,KAAKgQ,EACd,CAGO,SAASwY,GAAYxF,EAAcyF,GACnCJ,IACLC,GAAO,CAAEtF,OAAM0F,QAASC,YAAY3M,MAAQkM,GAAWO,WACzD,CAEO,SAASG,GAAY5F,EAAcyF,GACxC,IAAKJ,GAAc,MAAO,OAC1B,MAAMQ,EAAMd,GACNe,EAAQH,YAAY3M,MACpBhM,EAAqB,CAAEgT,OAAM0F,QAASI,EAAQZ,GAAWO,WAE3DX,GAAS5rB,OAASyrB,KACpBG,GAASrX,OAAOqX,GAAShgB,SAASihB,OAAO5hB,OACzC6gB,MAEFF,GAAS3lB,IAAI6N,GACb,IAAIgZ,GAAW,EACf,OAAO,W,IAACpjB,EAAS,UAATA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAS,KACf,GAAIojB,EAAU,OAGd,GAFAA,GAAW,EACXlB,GAASrX,OAAOT,GACZ6Y,IAAQd,GAAW,OACvB,MAAMkB,EAASN,YAAY3M,MAAQ8M,EAC7BI,EAAIC,GAAOnG,GACjBkG,EAAExoB,SACFwoB,EAAEzb,IAAMwb,EACRC,EAAEE,MAAQ7rB,KAAKkX,IAAIyU,EAAEE,MAAOH,GACb,UAAXrjB,GAAoBsjB,EAAEG,SAC1Bf,GAAO,IAAKtY,EAAOiZ,SAAQrjB,UAC7B,CACF,CAEA,SAASujB,GAAOnG,GACd,IAAIkG,EAAIjB,GAAQ/iB,IAAI8d,GAKpB,OAJKkG,IACHA,EAAI,CAAExoB,OAAQ,EAAG+M,GAAI,EAAG2b,MAAO,EAAGC,OAAQ,GAC1CpB,GAAQ/hB,IAAI8c,EAAMkG,IAEbA,CACT,CAGO,SAASI,GAAItG,G,IAActiB,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAS,EACpC2nB,KACLc,GAAOnG,GAAMtiB,QAAUA,EACzB,CAGO7F,eAAe0uB,GAAQvG,EAAcwG,EAAwBf,GAClE,IAAKJ,GAAc,OAAOmB,IAC1B,MAAM/qB,EAAQmqB,GAAY5F,EAAMyF,GAChC,IACE,MAAMgB,QAAkBD,IAExB,OADA/qB,IACOgrB,CACT,CAAE,MAAOljB,GAEP,MADA9H,EAAM,SACA8H,CACR,CACF,CA2BA,MAAMmjB,GAA2C,CAAC,EAO3C,SAASC,GAAmB3G,EAAc4G,GAC/C,IAAKvB,GAAc,MAAO,OAE1B,GADAqB,GAAU1G,GAAQ4G,EACI,oBAAXvC,OAAwB,CACjC,MAAM/nB,EAAO,OAA+DuqB,WACxEvqB,IAAMA,EAAK0jB,GAAQ4G,EACzB,CACA,MAAO,KACL,GAAIF,GAAU1G,KAAU4G,WACjBF,GAAU1G,GACK,oBAAXqE,QAAwB,CACjC,MAAM/nB,EAAO,OAA+DuqB,WACxEvqB,UAAaA,EAAK0jB,EACxB,EAEJ,CAEA,SAAS8G,KACP,MAAMC,EAAKpB,YAAY3M,MACvB,MAAO,CACLgO,OAAQ,EACRC,aAAcF,EAAK7B,GACnBF,cACAC,QAASzhB,OAAO0jB,YAAY,IAAIjC,IAAS5gB,IAAK,I,IAAC2b,EAAMkG,GAAE,E,MAAK,CAAClG,EAAM,IAAKkG,OACxEtB,eAAgB,IAAIC,MAAgBD,IAAgBnoB,KAAK,CAACP,EAAGC,IAAMD,EAAEwpB,QAAUvpB,EAAEupB,SACjFZ,SAAU,IAAIA,IAAUzgB,IAAKuF,IAAO,IAAKA,EAAGud,QAASJ,EAAK7B,GAAYtb,EAAE8b,WACxEgB,UAAWljB,OAAO0jB,YAChB1jB,OAAOC,QAAQijB,IAAWriB,IAAK,I,IAAC2b,EAAM4G,GAAG,EACvC,IACE,MAAO,CAAC5G,EAAM4G,IAChB,CAAE,MACA,MAAO,CAAC5G,EAAM,CAAErC,KAAM,6BACxB,KAIR,CAmCA,GAAI0H,IAAkC,oBAAXhB,OAAwB,CAgBjD,GAfCA,OAA8CwC,WAAa,CAC1DO,MA3FJ,WACErC,KACAE,GAAQoC,QACRzC,GAAehnB,OAAS,EACxBinB,GAAYjnB,OAAS,EACrBknB,GAASuC,QACTrC,GAAa,EACbE,GAAYS,YAAY3M,KAC1B,EAoFIsO,QAzBJ,WACE,MAAMC,EAAahtB,KAAK4c,MAAMwO,YAAY3M,MAAQkM,IAC5CsC,EAAS,IAAIvC,GAAQxhB,WACxBhH,KAAK,CAACP,EAAGC,IAAMA,EAAE,GAAGsO,GAAKvO,EAAE,GAAGuO,IAAMtO,EAAE,GAAGuB,OAASxB,EAAE,GAAGwB,QACvD2G,IAAK,I,IAAC2b,EAAMkG,GAAE,E,MAAM,CACnBuB,SAAUzH,EACVtiB,OAAQwoB,EAAExoB,OACV,YAAanD,KAAK4c,MAAM+O,EAAEzb,IAC1B,cAAeyb,EAAExoB,OAASnD,KAAK4c,MAAM,EAAG1M,GAAKyb,EAAExoB,OAAU,IAAM,GAAK,EACpE,SAAUnD,KAAK4c,MAAM+O,EAAEE,OACvBC,OAAQH,EAAEG,UAGd/iB,QAAQokB,IAAI,cAAcH,oBAE1BjkB,QAAQ+Q,MAAMmT,GACdlkB,QAAQokB,IACN,mFACAZ,KAEJ,EAMIA,YAGAjC,YAAa,IAAMA,GAAYxgB,IAAKuF,IAAO,IAAKA,KAChDqd,aAAc,IAAMtB,YAAY3M,MAAQkM,GACxCpJ,SA1CJ,WACE,MAAMjf,EAAM4e,IAAIC,gBACd,IAAIF,KAAK,CAAC5Z,KAAKY,UAAUskB,KAAY,KAAM,IAAK,CAAEjmB,KAAM,sBAEpD5H,EAAOwI,SAASma,cAAc,KACpC3iB,EAAK4iB,KAAOhf,EACZ5D,EAAK6iB,SAAW,aAAY,IAAIlX,MAAOC,cAAc4C,QAAQ,QAAS,YACtExO,EAAK+iB,QACLqI,OAAOzZ,WAAW,IAAM6Q,IAAIS,gBAAgBrf,GAAM,IACpD,EAkCIooB,cACGyB,IAELxB,GAAYS,YAAY3M,MAGW,oBAAxB2O,oBACT,IAAK,MAAM9mB,IAAQ,CAAC,WAAY,YACzB8mB,oBAAoBC,qBAAqBlhB,SAAS7F,IACtC,IAAI8mB,oBAAqBE,IACxC,IAAK,MAAMC,KAASD,EAAKE,aAAc,CACrC,GAAID,EAAME,UAAY9C,GAAW,SACjC,GAAwB,aAApB4C,EAAMG,UAA0B,CAClC3C,GAAO,CACLtF,KAAM,mBACN0F,QAASoC,EAAME,UAAY9C,GAC3Be,OAAQ6B,EAAMI,WAEhB,QACF,CACA,MAAMC,EAAWL,EACjB,IAAIjrB,EACJ,IACEA,EAAM,IAAI4e,IAAI0M,EAASlkB,KACzB,CAAE,MACA,QACF,CACA,MAAMmkB,EAAMvrB,EAAIwrB,SAAWhE,OAAOC,SAAS+D,QAAUxrB,EAAIyrB,SAASlhB,WAAW,SACvEmhB,EAAM,YAAY3tB,KAAKiC,EAAIyrB,UACjC,IAAKF,IAAQG,EAAK,SAClB,MAAMC,EAAqBL,EAASM,cAAgB,EACpDnD,GAAO,CACLtF,KAAMoI,EAAM,cAAgB,cAC5B1C,QAASyC,EAASH,UAAY9C,GAC9Be,OAAQkC,EAASD,SACjBzC,QAAS,CACPiD,cAAeF,EACfG,OAAQH,EAAqBL,EAASM,cAAgBN,EAASS,aAAe,KAC9EC,WAAYL,EAAqBL,EAASW,YAAcX,EAASM,cAAgB,KACjFM,cAAeP,EAAqBL,EAASa,aAAe,OAGlE,IAEOC,QAAQ,CAAEpoB,SAIvByC,QAAQokB,IAAI,mFACd,CCjRO,SAASwB,GACdC,EACAC,GAEA,GAAIA,GAAU,EAAG,OACjB,MAAM1qB,EAAI,IAAIkG,KAAKukB,EAAe,aAIlC,OAHAzqB,EAAE2qB,SAAS3qB,EAAE4qB,YAAcF,EAAS,GAAI,GAGjC,GAFG1qB,EAAE6qB,iBACFrP,OAAOxb,EAAE4qB,WAAa,GAAGE,SAAS,EAAG,SAEjD,CAGA,MAAMC,GAAkB,CACtB,MAAO,MAAO,MAAO,MAAO,MAAO,MACnC,MAAO,MAAO,MAAO,MAAO,MAAO,OAG/BC,GAAiB,CACrB,UAAW,WAAY,QAAS,QAAS,MAAO,OAChD,OAAQ,WAAY,YAAa,UAAW,WAAY,YAG1D,SAASC,GAAUjrB,GACjB,MAAO,GAAG+qB,GAAgB/qB,EAAE4qB,eAAe5qB,EAAE6qB,eAC/C,CAEA,SAASK,GAAUlrB,GACjB,OAAO+qB,GAAgB/qB,EAAE4qB,WAC3B,CAUO,SAASO,GACdC,EACAV,GAEA,MAAMW,EAAM,IAAInlB,KAAKklB,EAAuB,aAC5C,GAAe,IAAXV,EAAc,OAAOO,GAAUI,GACnC,GAAe,IAAXX,EAAc,MAAO,OAAOO,GAAUI,KAC1C,MAAMhN,EAAQ,IAAInY,KAAKmlB,GAEvB,OADAhN,EAAMsM,SAAStM,EAAMuM,YAAcF,EAAS,GAAI,GAC5CrM,EAAMwM,gBAAkBQ,EAAIR,cACvB,GAAGK,GAAU7M,QAAY4M,GAAUI,KAErC,GAAGJ,GAAU5M,QAAY4M,GAAUI,IAC5C,CAGO,SAASC,GAAgBb,GAC9B,OAAOS,GAAU,IAAIhlB,KAAKukB,EAAe,aAC3C,CChDA,SAASc,GAAa5P,GACpB,IAAK,MAAMvD,KAAMuD,EAAU,CACzB,MAAMxd,EAAM,EAAoBqtB,QAC5BrtB,IACFstB,GAASjnB,IAAI4T,EAAGla,GAAI,CAAEC,MAAKutB,UAAWxlB,KAAKoU,QAC3CqR,GAAgBnnB,IAAI4T,EAAGla,GAAI,CACzBC,IAAK,EAAoBytB,iBAAmBztB,EAC5CutB,UAAWxlB,KAAKoU,SAKpB,MAAMuR,EAAY,EAAoBC,uBAClCD,GAAqC,MAAxBzT,EAAG2T,mBAClBJ,GAAgBnnB,IAAI4T,EAAG2T,kBAAmB,CAAE5tB,IAAK0tB,EAAWH,UAAWxlB,KAAKoU,OAEhF,CACF,CAGO,SAAS0R,GAAWzsB,GAIzB,MAAOO,EAAMqgB,GAAS5gB,EAAIC,MAAM,KAAKmG,IAAIlG,QACnCwsB,EAAa,IAAI/lB,KAAKA,KAAKgmB,IAAIpsB,EAAMqgB,EAAO,IAAIgM,aACtD,MAAO,GAAGrsB,KAAQ0b,OAAO2E,GAAO2K,SAAS,EAAG,QAAQtP,OAAOyQ,GAAYnB,SAAS,EAAG,MACrF,CAUA,IAAIsB,GAAsB,IAAIxtB,IAMvB,SAASytB,GAAqBzvB,GACnC,OAAgB,MAATA,GAAiBwvB,GAAoBvtB,IAAIjC,EAClD,CAEA,MAAM0vB,GAAgB,IAAIzqB,IACpB0qB,GAAe,IAAI1qB,IACnB2qB,GAAiB,KACjBC,GAAmB,IAAI5qB,IAE7B,SAAS6qB,GAAiB7T,EAAc8T,GACtC,MAAO,GAAG9T,KAAQ8T,IACpB,CAEOxzB,eAAeyzB,GACpBC,EACAF,GAEA,MAAMhqB,EAAM+pB,GAAiBG,EAAYF,GACnCG,EACJ5mB,KAAKoU,OAASiS,GAAa/oB,IAAIb,KAAQ,KAAa6pB,GAChDF,GAAc9oB,IAAIb,QAClBgE,EACN,GAAImmB,EAEF,OADAlF,GAAI,0BACGkF,EAET,MAAMC,EAAWN,GAAiBjpB,IAAIb,GACtC,GAAIoqB,EAEF,OADAnF,GAAI,0CACGmF,EAETnF,GAAI,0BAEJ,MAAMoF,EAAchB,GAAWa,GACzBI,EAAc,CAClB/oB,OAAQ,QACRgpB,UAAWb,GAAqBQ,GAC5BA,EACArC,GAAyBqC,EAAYF,GACzCQ,QAASH,EACTI,gBAAgB,EAChBC,MAAM,EACNC,iBAAiB,EACjBC,MAAO,KA6BHC,EAAU3F,GAAK,mBAzBLwE,GAAqBQ,GACjC1zB,UACE,MAAOs0B,EAAUC,SAAwB1hB,QAAQ2hB,IAAI,CACnD3P,GAAgB,CAAE7S,cAAc,EAAMpD,MAAOklB,IAC7CjP,GAAgB,CACd7S,cAAc,EACdpD,MAAO,CACL7D,OAAQ,QACR6W,UAAW,CAAC,gBACZmS,UAAWL,EACXM,QAASH,EACTK,MAAM,EACNC,iBAAiB,EACjBC,MAAO,SAIPzU,EAAU2U,EAAS5iB,MAAM+iB,SAAW,GACpCC,EAAS,IAAIjvB,IAAIka,EAAQnT,IAAKyS,GAAOA,EAAG4D,iBACxC8R,GAAcJ,EAAe7iB,MAAM+iB,SAAW,IAAI5wB,OACrDob,IAAQyV,EAAOhvB,IAAIuZ,EAAG4D,iBAEzB,MAAO,CAAEnR,KAAM,CAAE+iB,QAAS,IAAI9U,KAAYgV,MAE5C,IAAM9P,GAAgB,CAAE7S,cAAc,EAAMpD,MAAOklB,IACL,CAChDrwB,MAAOiwB,EACPF,oBAEClzB,KAAM,I,IAAA,KAAEoR,GAAM,EACb,IAAKA,GAAM+iB,QAAS,MAAM,IAAI3pB,MAAM,sCACpC6iB,GAAY,6BAA8B,CACxClqB,MAAOiwB,EACP5N,MAAM,EACNtD,SAAU9Q,GAAM+iB,SAAS1uB,QAAU,IAErC,MAAM0uB,EAAU/iB,GAAM+iB,SAAW,GAIjC,OAHArC,GAAaqC,GACbtB,GAAc9nB,IAAI7B,EAAKirB,GACvBrB,GAAa/nB,IAAI7B,EAAKuD,KAAKoU,OACpBsT,IAERpO,MAAO3a,IAEN,MADAiiB,GAAY,6BAA8B,CAAElqB,MAAOiwB,EAAY5N,MAAM,IAC/Dpa,IAEP4a,QAAQ,IAAMgN,GAAiB1d,OAAOpM,IAGzC,OADA8pB,GAAiBjoB,IAAI7B,EAAK6qB,GACnBA,CACT,CA8BO,SAASO,GAAiBlB,G,IAA4BF,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAkB,EAC7E,MAAOhR,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,GAyCvC,OAvCAC,EAAAA,EAAAA,WAAU,KACR,IAAK6zB,EAGH,OAFAmB,EAAY,SACZE,GAAW,GAIb,IAAI5qB,GAAY,EAGhB,MAAMX,EAAM+pB,GAAiBG,EAAYF,GACnCG,EACJ5mB,KAAKoU,OAASiS,GAAa/oB,IAAIb,KAAQ,KAAa6pB,GAChDF,GAAc9oB,IAAIb,QAClBgE,EACN,OAAImmB,GACFkB,EAAYlB,QACZoB,GAAW,KAIbA,GAAW,GAEXtB,GAAcC,EAAYF,GACvBlzB,KAAMm0B,IACAtqB,GAAW0qB,EAAYJ,KAE7BpO,MAAM,KACAlc,GAAW0qB,EAAY,MAE7BvO,QAAQ,KACFnc,GAAW4qB,GAAW,KAGxB,KACL5qB,GAAY,KAEb,CAACupB,EAAYF,IAET,CAAEhR,WAAUsS,UACrB,CAGA,MAAME,GAAmB,IAAItsB,IACvBusB,GAAkB,IAAIvsB,IACtBwsB,GAAsB,IAAIxsB,IA4CzB,SAASysB,GAAezB,G,IAA4BF,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAkB,EAAG/zB,IAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,KAAAA,UAAAA,GAC9E,MAAO+iB,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,GAsCvC,OApCAC,EAAAA,EAAAA,WAAU,KACR,IAAK6zB,IAAej0B,EAGlB,OAFAo1B,EAAY,SACZE,GAAW,GAGb,IAAI5qB,GAAY,EAEhB,MAAMX,EAAM+pB,GAAiBG,EAAYF,GACnCG,EACJ5mB,KAAKoU,OAAS8T,GAAgB5qB,IAAIb,KAAQ,KAAa6pB,GACnD2B,GAAiB3qB,IAAIb,QACrBgE,EACN,OAAImmB,GACFkB,EAAYlB,QACZoB,GAAW,KAIbA,GAAW,GAjEf/0B,eACE0zB,EACAF,GAEA,MAAMhqB,EAAM+pB,GAAiBG,EAAYF,GACnCG,EACJ5mB,KAAKoU,OAAS8T,GAAgB5qB,IAAIb,KAAQ,KAAa6pB,GACnD2B,GAAiB3qB,IAAIb,QACrBgE,EACN,GAAImmB,EAAQ,OAAOA,EACnB,MAAMC,EAAWsB,GAAoB7qB,IAAIb,GACzC,GAAIoqB,EAAU,OAAOA,EAErB,MAAMC,EAAchB,GAAWa,GACzBW,EAAUxP,GAAgB,CAC9BjW,MAAO,CACL7D,OAAQ,QACRgpB,UAAW1C,GAAyBqC,EAAYF,GAChDQ,QAASH,EACTjS,UAAW,CAAC,OACZsS,MAAM,EACNC,iBAAiB,EACjBC,MAAO,OAGR9zB,KAAM,I,IAAA,KAAEoR,GAAM,EACb,MAAM+iB,EAAU/iB,GAAM+iB,SAAW,GAIjC,OAHArC,GAAaqC,GACbO,GAAiB3pB,IAAI7B,EAAKirB,GAC1BQ,GAAgB5pB,IAAI7B,EAAKuD,KAAKoU,OACvBsT,IAERnO,QAAQ,IAAM4O,GAAoBtf,OAAOpM,IAG5C,OADA0rB,GAAoB7pB,IAAI7B,EAAK6qB,GACtBA,CACT,CA8BIe,CAAiB1B,EAAYF,GAC1BlzB,KAAMm0B,IACAtqB,GAAW0qB,EAAYJ,KAE7BpO,MAAM,KACAlc,GAAW0qB,EAAY,MAE7BvO,QAAQ,KACFnc,GAAW4qB,GAAW,KAGxB,KACL5qB,GAAY,KAEb,CAACupB,EAAYF,EAAiB/zB,IAE1B,CAAE+iB,WAAUsS,UACrB,CAGA,MAAMxC,GAAW,IAAI5pB,IACf8pB,GAAkB,IAAI9pB,IACtB2sB,GAAc,IAAI3sB,IAClB4sB,GAAa,IAEZt1B,eAAeu1B,GAAUC,GAC9B,MAAM7B,EAASrB,GAASjoB,IAAImrB,GAC5B,GAAI7B,GAAU5mB,KAAKoU,MAAQwS,EAAOpB,UAAY+C,GAE5C,OADA7G,GAAI,qBACGkF,EAAO3uB,IAEhB,MAAM4uB,EAAWyB,GAAYhrB,IAAImrB,GACjC,GAAI5B,EAAU,OAAOA,EAErB,MAAMS,EAAU3F,GACd,cACA,KAAM+G,QVtCsEnoB,EUsC5C,CAAE8B,KAAM,CAAErK,GAAIywB,KVrC9B9kB,QAAUA,GAAQrG,IAA+D,CAC7FuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,iCACFsI,IAX8B,IAAuCA,GUuC5E,CAAEkoB,cAEDl1B,KAAM,I,IAAA,KAAEoR,GAAM,EAEb,MAAM1M,EAAO0M,GAAqC1M,IAClD,OAAIA,GACFstB,GAASjnB,IAAImqB,EAAW,CAAExwB,MAAKutB,UAAWxlB,KAAKoU,QAC/CqR,GAAgBnnB,IAAImqB,EAAW,CAC7BxwB,IAAK,GAA+C0wB,aAAe1wB,EACnEutB,UAAWxlB,KAAKoU,QAEXnc,GAEF,OAERqhB,MAAM,IAAM,MACZC,QAAQ,IAAM+O,GAAYzf,OAAO4f,IAGpC,OADAH,GAAYhqB,IAAImqB,EAAWnB,GACpBA,CACT,CAGOr0B,eAAe21B,GAAiBH,GACrC,MAAM7B,EAASnB,GAAgBnoB,IAAImrB,GACnC,GAAI7B,GAAU5mB,KAAKoU,MAAQwS,EAAOpB,UAAY+C,GAAY,OAAO3B,EAAO3uB,IACxE,MAAM4wB,QAAiBL,GAAUC,GAC3BK,EAAYrD,GAAgBnoB,IAAImrB,GACtC,OAAOK,GAAa9oB,KAAKoU,MAAQ0U,EAAUtD,UAAY+C,GAAaO,EAAU7wB,IAAM4wB,CACtF,C,qCCtVA,SAASE,GAASC,GAChB,MAAMC,EAAIjhB,SAASghB,EAAI9xB,MAAM,GAAI,IACjC,MAAO,CAAE+xB,GAAK,GAAM,IAAOA,GAAK,EAAK,IAAU,IAAJA,EAC7C,CAGO,MAAMC,GAAuC,CAClD,CAAEC,OAAQ,EAAGla,MAAO,WACpB,CAAEka,OAAQ,GAAKla,MAAO,WACtB,CAAEka,OAAQ,GAAKla,MAAO,WACtB,CAAEka,MAAO,EAAGla,MAAO,WACnB,CAAEka,MAAO,GAAKla,MAAO,WACrB,CAAEka,MAAO,GAAKla,MAAO,WACrB,CAAEka,MAAO,EAAGla,MAAO,YACnBxP,IAAKsf,IAAU,IAAKA,EAAMqK,IAAKL,GAAShK,EAAK9P,UAGxC,SAASoa,GAAmBF,GACjC,MAAMG,EAAQJ,GACd,GAAIC,GAASG,EAAM,GAAGH,MAAO,OAAOG,EAAM,GAAGF,IAC7C,IAAK,IAAI9wB,EAAI,EAAGA,EAAIgxB,EAAMtwB,OAAS,EAAGV,IAAK,CACzC,MAAMixB,EAAOD,EAAMhxB,GACbkxB,EAAOF,EAAMhxB,EAAI,GACvB,GAAI6wB,GAASK,EAAKL,MAAO,CACvB,MAAMjrB,GAAKirB,EAAQI,EAAKJ,QAAUK,EAAKL,MAAQI,EAAKJ,OACpD,MAAO,CACLxzB,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KACrDzzB,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KACrDzzB,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KAEzD,CACF,CACA,OAAOE,EAAMA,EAAMtwB,OAAS,GAAGowB,GACjC,CAGO,SAASK,GAAmBN,GAEjC,OADUxzB,KAAKkX,KA9CmB,EA8CQlX,KAAK8S,IA9Cb,EA8CuC0gB,IA9CvC,GA+CI,EAA6B,GACrE,CC9CO,SAASO,GAAiBC,EAAWxc,EAAWC,GACrD,MAAM9Y,EAdsB,aAcSqB,KAAKi0B,IAAI,EAAGD,GAC3CE,EAA0B1c,EAAI7Y,EAAvB,aAEPw1B,EAhBgBC,aAgBS3c,EAAI9Y,EAEnC,MAAO,CAACu1B,EADKC,EAAOx1B,EAFPu1B,EAAOv1B,EAGMw1B,EAC5B,CCAA,MAAME,GAAY,IAId1K,GAAgB,oBAAqB,CAAC,GAAI,KAC3CG,OAAewK,gBAAkBh3B,MAAO+E,EAAY2xB,EAAWxc,EAAWC,KACzE,MAAMnV,EAAM,GAAGwnB,OAAOC,SAAS+D,yBAAyBzrB,QAClDkyB,EAAMC,GAAoBlyB,GAChC,IAAKiyB,EAAK,MAAM,IAAInsB,MAAM,gCAC1B,MAAMqsB,QAAeC,GAAmBpyB,EAAKiyB,EAAKP,EAAGxc,EAAGC,GAClDkd,EAAS,IAAIC,gBAAgBP,GAAWA,IACxCQ,EAAUF,EAAOG,WAAW,MAClCD,EAAQE,aAAaN,EAAQ,EAAG,GAChC,MAAMzT,QAAa2T,EAAOK,cAAc,CAAE1uB,KAAM,cAC1C2uB,QAAeC,kBAAkBlU,GACvC6T,EAAQM,UAAU,EAAG,EAAGd,GAAWA,IACnCQ,EAAQO,UAAUH,EAAQ,EAAG,GAC7BA,EAAOI,QACP,MAAMC,EAAUT,EAAQU,aAAa,EAAG,EAAGlB,GAAWA,IAAWrlB,KACjE,IAAK,IAAIrM,EAAI,EAAGA,EAAI2yB,EAAQjyB,OAAQV,IAClC,GAAI2yB,EAAQ3yB,KAAO8xB,EAAOzlB,KAAKrM,GAAI,MAAM,IAAIyF,MAAM,gCACrD,MAAMotB,QAAeC,OAAOC,OAAOF,OAAO,UAAWf,EAAOzlB,MACtD2mB,EAAW,IAAI,IAAIC,WAAWJ,IACjC1rB,IAAKwpB,GAAMA,EAAEuC,SAAS,IAAI5G,SAAS,EAAG,MACtC3qB,KAAK,IACFwxB,EAAQ,IAAIF,iBAAiB5U,EAAK+U,eACxC,IAAIC,EAAS,GACb,IAAK,MAAMC,KAAQH,EAAOE,GAAUrW,OAAOuW,aAAaD,GACxD,MAAO,CAAEE,IAAK7qB,KAAK0qB,GAASL,cAMhC,MAAMS,GAAe,IAAIpwB,IAGnBqwB,GAAgB,IAAIrwB,IAMpBswB,GAAmB,IAAItwB,IAEtB,SAASuwB,GAAoBC,EAAqBC,GACvDH,GAAiB3tB,IAAI6tB,EAAaC,EACpC,CAEO,SAASC,GAAsBF,GACpCF,GAAiBpjB,OAAOsjB,EAC1B,CAEO,SAAShC,GAAoBgC,GAClC,OAAOF,GAAiB3uB,IAAI6uB,EAC9B,CAYA,IAAIG,GAA0B,UAmB1BC,IAAgB,EAkBpB,MAAMC,GAAkB,IAAI7wB,IAErB,SAAS8wB,GAAaN,EAAqBO,GACnC,MAATA,GAAiBA,EAAQ,EAAGF,GAAgBluB,IAAI6tB,EAAaO,GAC5DF,GAAgB3jB,OAAOsjB,EAC9B,CAEO,SAASQ,GAAaR,GAC3B,OAAOK,GAAgBlvB,IAAI6uB,EAC7B,CAGA,IAAIS,GACJ,SAASC,KACP,IAAKD,GAAM,CACTA,GAAO,IAAIE,GAAAA,EACX,MAAMC,EAASH,GAAKG,OAAOC,KAAKJ,IAChCA,GAAKG,OAAS,CAACE,EAAW/lB,IACxBya,GAAK,uCAAwC,IAAMoL,EAAOE,EAAW/lB,GACzE,CACA,OAAO0lB,EACT,CAqBA,MAAMM,GAAalD,OAuBbmD,GANN,WACE,MACMC,EAAe,KADV,UAAoDC,cAAgB,GACpD,KAAO,KAClC,OAAO13B,KAAK8S,IAAI,WAAoB9S,KAAKkX,IAAI,UAAmBugB,GAClE,CAEqBE,GAEfC,GAAwB53B,KAAK8S,IAAI,SAAiC,GAAf0kB,IACnDK,GAAoB73B,KAAK8S,IAAI,SAAiC,GAAf0kB,IAC/CM,GAAqBN,GAAeI,GAAwBC,GAC5DE,GAAgB,IC1Lf,M,SACGC,MAAQ,IAAIhyB,IACZiyB,QAAU,IAAIjyB,IACd8vB,MAAQ,EAEhB,WAAAoC,CAAY,G,KAAiBC,SAAAA,CAAmB,CAEhD,UAAMvmB,CAAK9K,EAAasxB,GACtB,MAAMnH,EAASoH,KAAKL,MAAMrwB,IAAIb,GAC9B,GAAImqB,EAIF,OAHAoH,KAAKL,MAAM9kB,OAAOpM,GAClBuxB,KAAKL,MAAMrvB,IAAI7B,EAAKmqB,GACpBlF,GAAI,qBACGkF,EAET,MAAMgH,EAAUI,KAAKJ,QAAQtwB,IAAIb,GACjC,GAAImxB,EAEF,OADAlM,GAAI,uBACGkM,EAETlM,GAAI,oBACJ,MAAMna,EAAOzB,QAAQC,UAAUxS,KAAKw6B,GACpCC,KAAKJ,QAAQtvB,IAAI7B,EAAK8K,GACtB,IACE,MAAM0mB,QAAc1mB,EACpB,KAAOymB,KAAKvC,MAAQwC,EAAMtpB,KAAKupB,WAAaF,KAAKF,UAAYE,KAAKL,MAAMr5B,MAAM,CAC5E,MAAM65B,EAASH,KAAKL,MAAMS,OAAOjN,OAAO5hB,MACxCyuB,KAAKvC,OAASuC,KAAKL,MAAMrwB,IAAI6wB,GAASxpB,KAAKupB,WAC3CF,KAAKL,MAAM9kB,OAAOslB,GAClBzM,GAAI,uBACN,CAKA,OAJIuM,EAAMtpB,KAAKupB,YAAcF,KAAKF,WAChCE,KAAKL,MAAMrvB,IAAI7B,EAAKwxB,GACpBD,KAAKvC,OAASwC,EAAMtpB,KAAKupB,YAEpBD,CACT,CAAE,QACAD,KAAKJ,QAAQ/kB,OAAOpM,EACtB,CACF,CAGA4xB,MAAAA,CAAOC,EAAer2B,G,WACpB,MAAMs2B,EAAWD,EAAKC,SAASvB,KAAKsB,GAC9BE,EAAS,IAAI7yB,IA2BnB,OA1BA2yB,EAAKC,SAAW,W,IAACE,EAAQ,UAARA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAQ,EACnBb,EAAUY,EAAOlxB,IAAImxB,GAuBzB,OAtBKb,IACHA,EAAUW,EAASE,GAChBl7B,KAAMm7B,IACL,MAAMC,EAAYD,EAAME,eAAe5B,KAAK0B,GAW5C,OAVAA,EAAME,eAAiB,CAACzhB,EAAGC,EAAGyhB,EAAQC,EAAS1oB,IAEzCA,EAAeuoB,EAAUxhB,EAAGC,EAAGyhB,EAAQC,EAAS1oB,GAC7C,EAAKmB,KAAK,GAAGtP,KAAOw2B,KAASthB,KAAKC,KAAKyhB,IAAU,IACtDlN,GACE,gCACA,IAAMgN,EAAUxhB,EAAGC,EAAGyhB,EAAQC,KAI7BJ,IAERpV,MAAO3a,IAEN,MADA6vB,EAAO3lB,OAAO4lB,GACR9vB,IAEV6vB,EAAOlwB,IAAImwB,EAAOb,IAEbA,CACT,EACOU,CACT,GDkHsCd,IAElCuB,GAAmBp5B,KAAKiZ,MAA4B,GAAtB,GAA6Bse,IAC3D8B,GAAoBr5B,KAAKiZ,MAA4B,GAAtB,GAA6Bse,IAC5D+B,GAAiB,IAAItzB,IACrBuzB,GAAiB,IAAIvzB,IAC3B,IAAIwzB,GAAiB,EAQrB,MAAMC,GAAe,IAAIzzB,IAEnB0zB,GAAe,IAAI1zB,IAOnB2zB,GAAiB,IAAI3zB,IAE3B,SAAS4zB,GAAeC,EAA+B/yB,EAAalH,GAClEi6B,EAAM3mB,OAAOpM,GACb+yB,EAAMlxB,IAAI7B,EAAKlH,EACjB,CAEA,SAASk6B,GAAMD,EAA+B3iB,EAAauO,GACzD,KAAOoU,EAAMl7B,MAAQuY,GAAK,CACxB,MAAM6iB,EAASF,EAAMpB,OAAOjN,OAAO5hB,MACnC,QAAekB,IAAXivB,EAAsB,OAC1BF,EAAM3mB,OAAO6mB,GACbhO,GAAI,gCAAgCtG,KACtC,CACF,CAMA,SAASuU,GAAkBlzB,EAAa7E,GACtC,MAAMg4B,EAASR,GAAa9xB,IAAIb,GAChC,GAAImzB,EAEF,OADAL,GAAeH,GAAc3yB,EAAKmzB,GAC3BA,EAET,MAAMC,EAAaR,GAAa/xB,IAAIb,GACpC,OAAKozB,EACDj4B,GACF23B,GAAeF,GAAc5yB,EAAKozB,GAC3BA,IAETR,GAAaxmB,OAAOpM,GACpBgzB,GAAML,GAAcL,GAAkB,UACtCK,GAAa9wB,IAAI7B,EAAKozB,GACfA,QARP,CASF,CA6GA,SAASC,GAAW73B,GAClB,IAAIirB,EAAQ6I,GAAazuB,IAAIrF,GAQ7B,OAPKirB,IACHxB,GAAI,8BACJwB,EAAQ,CACNoL,KAAM3M,GAAK,kBAAmB1uB,SAAYy6B,GAAcW,aAAa0B,EAAAA,GAAAA,IAAQ93B,GAAMA,KAErF8zB,GAAaztB,IAAIrG,EAAKirB,IAEjBA,EAAMoL,IACf,CAEAr7B,eAAe+8B,GAAW/3B,GACxB,IAAI4uB,EAAWmF,GAAc1uB,IAAIrF,GAcjC,OAbK4uB,IACHA,EAAW,WACT,MAAMyH,QAAawB,GAAW73B,GACxBy2B,QAAcJ,EAAKC,WACnB0B,EAAWvB,EAAMwB,gBAAgB,GACvC,MAAO,CACLpiB,WAA2BrN,IAApBwvB,GAAUE,MAAsBC,WAAWH,EAASE,OAAS,EACpEnR,YAA6Bve,IAArBwvB,GAAUI,OAAuBD,WAAWH,EAASI,QAAU,EACvEC,OAAQ5B,EAAM6B,sBAAmB9vB,EAEpC,EATU,GAUXurB,GAAc1tB,IAAIrG,EAAK4uB,IAElBA,CACT,CAEA5zB,eAAeu9B,GAAYv4B,EAAa0xB,EAAWxc,EAAWC,GAC5D,MAAMkhB,QAAawB,GAAW73B,IACxB,OAAEq4B,SAAiBN,GAAW/3B,GAE9Bw4B,OAAuBhwB,IAAX6vB,GAAwB/2B,OAAO0O,MAAMqoB,GAAUI,IAAWJ,EAU5E,aATmBhC,EAAKqC,YAAY,CAClCC,KAAMlH,GAAiBC,EAAGxc,EAAGC,GAC7BpB,MAAOge,GACP/d,OAAQ+d,GACR6G,YAAY,EACZC,eAAgB,UAChBlE,KAAMC,KACN4D,aAGJ,CAQOx9B,eAAe89B,GACpB94B,EACA24B,EACAI,EACAC,GAEA,MAAM3C,QAAawB,GAAW73B,IACxB,MAAE6V,EAAK,OAAEkR,EAAM,OAAEsR,SAAiBN,GAAW/3B,GAC7Cw4B,OAAuBhwB,IAAX6vB,GAAwB/2B,OAAO0O,MAAMqoB,GAAUI,IAAWJ,EACtEY,QAAa5C,EAAKqC,YAAY,CAClCC,OACA5kB,MAAOglB,EACP/kB,OAAQglB,EACRJ,YAAY,EACZC,eAAgB,UAChBlE,KAAMC,KACN4D,cAEI34B,EAAM,IAAIq5B,aAAaH,EAAUC,GACvC,IAAK,IAAI34B,EAAI,EAAGA,EAAIR,EAAIkB,OAAQV,IAAK,CACnC,MAAM/C,EAAS27B,EAAI54B,GACnBR,EAAIQ,GAAK84B,GAAS77B,EAAQ+6B,GAAUe,IAAM97B,EAASuY,EAAQkR,CAC7D,CACA,OAAOlnB,CACT,CAGA7E,eAAeq+B,GAAkBr5B,EAAa0xB,EAAWxc,EAAWC,GAClE,MAAM3Q,EAAM,GAAGxE,KAAO0xB,KAAKxc,KAAKC,IAC1BwZ,EAASqI,GAAe3xB,IAAIb,GAClC,GAAImqB,EAIF,OAHAlF,GAAI,4BACJuN,GAAepmB,OAAOpM,GACtBwyB,GAAe3wB,IAAI7B,EAAKmqB,GACjBA,EAET,MAAMgH,EAAUsB,GAAe5xB,IAAIb,GACnC,GAAImxB,EAEF,OADAlM,GAAI,8BACGkM,EAET,MAAMrmB,EAAOoa,GAAK,0BAA2B,IAAM6O,GAAYv4B,EAAK0xB,EAAGxc,EAAGC,IAC1E8hB,GAAe5wB,IAAI7B,EAAK8K,GACxB,IACE,MAAMgqB,QAAahqB,EACnB,KAAO4nB,GAAiBoC,EAAKrD,WAAaX,IAAyB0B,GAAe36B,MAAM,CACtF,MAAM65B,EAASc,GAAeb,OAAOjN,OAAO5hB,MAC5C4vB,IAAkBF,GAAe3xB,IAAI6wB,GAASD,WAC9Ce,GAAepmB,OAAOslB,GACtBzM,GAAI,8BACN,CAKA,OAJI6P,EAAKrD,YAAcX,KACrB0B,GAAe3wB,IAAI7B,EAAK80B,GACxBpC,IAAkBoC,EAAKrD,YAElBqD,CACT,CAAE,QAEArC,GAAermB,OAAOpM,EACxB,CACF,CAEA,SAAS20B,GAAS7xB,EAAe+wB,GAC/B,OAAK/2B,OAAOi4B,SAASjyB,SACNkB,IAAX6vB,GAAwB/wB,IAAU+wB,CAExC,CAkCAr9B,eAAeo3B,GACb8B,EACAC,EACAzC,EACAxc,EACAC,G,IACAxV,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,IAAAA,UAAAA,GAEA,MAAM65B,EAzMR,SAA2BtF,GAEzB,MAAO,CACLuF,SAAUnF,GACVoF,SAAUrF,GACVI,MAJYH,IAAmC,SAAlBD,QAIa7rB,EAA5BksB,GAAaR,GAE/B,CAkMgByF,CAAkBzF,GAC1B0F,EAjMR,SACE1F,EACAC,EACAzC,EACAxc,EACAC,EACAqkB,GASA,MAAO,GAAGtF,KAAeC,KALVqF,EAAMC,SACjB,WACmB,SAAnBD,EAAME,SACJ,QAAQF,EAAM/E,OAAS,MACvB+E,EAAME,YACoChI,KAAKxc,KAAKC,GAC5D,CAiLmB0kB,CAAa3F,EAAaC,EAAWzC,EAAGxc,EAAGC,EAAGqkB,GACzDM,EAAapC,GAAkBkC,EAAUj6B,GAC/C,GAAIm6B,EAEF,OADArQ,GAAI,4BACGqQ,EAGT,MAAMC,EAAS1C,GAAehyB,IAAIu0B,GAClC,GAAIG,EAAQ,CACVtQ,GAAI,8BACJ,MAAMuQ,QAAcD,EAGpB,OAAOrC,GAAkBkC,EAAUj6B,IAAYq6B,CACjD,CACAvQ,GAAI,2BAEJ,MAAMwQ,EASRj/B,eACEk5B,EACAC,EACAzC,EACAxc,EACAC,EACAxV,EACAi6B,EACAJ,GAEA,MAAOU,EAAUC,EAAQC,EAAWC,SAAiBxsB,QAAQ2hB,IAAI,CAC/D9F,GAAK,2BAA4B,IAAM6O,GAAYrE,EAAaxC,EAAGxc,EAAGC,IACtEkkB,GAAkBlF,EAAWzC,EAAGxc,EAAGC,GACnC4iB,GAAW7D,GACX6D,GAAW5D,KAIPmG,EAAO,IAAIC,kBAAkBpI,SAG7B,SAAEsH,EAAQ,SAAEC,EAAQ,MAAEjF,GAAU+E,EAChCgB,GAAQf,GAAyB,SAAbC,GL5crB,SAAqBvW,EAAcwG,GACxC,IAAKnB,GAAc,OAAOmB,IAC1B,MAAM/qB,EAAQmqB,GK8cL,kBL7cT,IACE,MAAMa,EAAYD,IAElB,OADA/qB,IACOgrB,CACT,CAAE,MAAOljB,GAEP,MADA9H,EAAM,SACA8H,CACR,CACF,CKqcE+zB,CAAS,EAAkB,KACzB,IAAK,IAAIp6B,EAAI,EAAGA,EAVH0xB,MAUe1xB,IAAK,CAC/B,MAAMq6B,EAAOR,EAAS75B,GACtB,GAAI84B,GAASuB,EAAMN,EAAU/B,QAAS,CACpCiC,EAAS,EAAJj6B,EAAQ,GAAK,EAClB,QACF,CACA,MAAMs6B,EAAOR,EAAO95B,GACpB,GAAI84B,GAASwB,EAAMN,EAAQhC,QAAS,CAIlCiC,EAAS,EAAJj6B,EAAQ,GAAK,EAClB,QACF,CACA,GAAIm6B,QAAkBhyB,IAAVisB,EAAqB,CAE/B6F,EAAS,EAAJj6B,EAAQ,GAAK,EAClB,QACF,CACA,MAAMu6B,EAAQF,EAAON,EAAUvkB,MAAQukB,EAAUrT,OAC3C8T,EAAYF,EAAON,EAAQxkB,MAAQwkB,EAAQtT,QAE1C9rB,EAAG4pB,EAAGvlB,GAAKm6B,EACdrI,GAAmBwJ,EAAQC,QACjBryB,IAAVisB,GACEqG,EAAAA,GAAAA,KAAYC,EAAAA,GAAAA,IAAUH,EAAQC,EAAWpG,KACzCuG,EAAAA,GAAAA,IAAkBJ,EAAQC,EAAWnB,GAC3CY,EAAS,EAAJj6B,EAAQ,GAAKpF,EAClBq/B,EAAS,EAAJj6B,EAAQ,GAAKwkB,EAClByV,EAAS,EAAJj6B,EAAQ,GAAKf,EAClBg7B,EAAS,EAAJj6B,EAAQ,GAAK,GACpB,IAGF,MAAM46B,EAAY,IAAIC,UAAUZ,EAAMvI,GAAWA,IAEjD,OAtWF,SAA2BvtB,EAAa81B,EAAiB36B,GACvD,GAAIA,EAGF,OAFA63B,GAAMJ,GAAcL,GAAmB,gBACvCK,GAAa/wB,IAAI7B,EAAK81B,GAGxB9C,GAAML,GAAcL,GAAkB,UACtCK,GAAa9wB,IAAI7B,EAAK81B,EACxB,CA6VEa,CAAkBvB,EAAUqB,EAAWt7B,GAChCs7B,CACT,CAzEiBG,CAAgBlH,EAAaC,EAAWzC,EAAGxc,EAAGC,EAAGxV,EAASi6B,EAAUJ,GACnFnC,GAAehxB,IAAIuzB,EAAUK,GAC7B,IACE,aAAaA,CACf,CAAE,QACA5C,GAAezmB,OAAOgpB,EACxB,CACF,CAjPA9P,GAAmB,QAbnB,WACE,MAAMuR,EAAYn7B,IAChB,MAAML,EAAgC,CAAC,EACvC,IAAK,MAAMy7B,KAAWp7B,EAAEi2B,OAAQ,CAC9B,MAAOoF,EAAO,CAAE,CAAEC,GAASF,EAAQj6B,MAAM,KACnC8hB,EAAOoY,EAAMl6B,MAAM,KAAK,GAAGA,MAAM,KAAKmO,OAAS+rB,GACpD17B,EAAIsjB,KAAU,IAAIhjB,KAAKq7B,EAC1B,CACA,OAAO37B,GAET,MAAO,CAAE83B,OAAQ0D,EAASlE,IAAex3B,QAAS07B,EAASjE,IAC7D,G,eEtTAqE,GAAAA,EAAM7U,KACJ,aACA,yMAGF,MAAM8U,IAAcD,EAAAA,GAAAA,GAAM,aAAc,aAEjC,SAASE,GAAUzmB,EAAWC,GACnC,OAAOumB,GAAYE,QAAQ,CAAC1mB,EAAGC,GACjC,CASO,SAAS0mB,GAAiBC,GAC/B,MAAO,IACFA,EACHC,SAAUD,EAAQC,SAASv0B,IAAKw0B,IAAa,IACxCA,EACHC,SAAUC,GAAkBF,EAAQC,aAG1C,CAEA,SAASE,GAAehe,GACtB,MAAOie,EAAK3W,GAAOkW,GAAUxd,EAAE,GAAIA,EAAE,IAGrC,OAAOA,EAAEpd,QAAU,EAAI,CAACq7B,EAAK3W,EAAKtH,EAAE,IAAM,CAACie,EAAK3W,EAClD,CAEA,SAASyW,GAAkBD,GACzB,OAAQA,EAASj4B,MACf,IAAK,QACH,MAAO,IAAKi4B,EAAU3nB,YAAa6nB,GAAeF,EAAS3nB,cAC7D,IAAK,aACL,IAAK,aACH,MAAO,IAAK2nB,EAAU3nB,YAAa2nB,EAAS3nB,YAAY9M,IAAI20B,KAC9D,IAAK,kBACL,IAAK,UACH,MAAO,IACFF,EACH3nB,YAAa2nB,EAAS3nB,YAAY9M,IAAKyN,GAASA,EAAKzN,IAAI20B,MAE7D,IAAK,eACH,MAAO,IACFF,EACH3nB,YAAa2nB,EAAS3nB,YAAY9M,IAAK+L,GACrCA,EAAQ/L,IAAKyN,GAASA,EAAKzN,IAAI20B,OAGrC,QACE,OAAOF,EAEb,CCjDA,MAAMI,GAAa,QA4HZ,SAASC,GAAOC,EAA6BrnB,EAAWC,GAC7D,IAAIqnB,GAAO,EACX,IAAK,MAAMvnB,KAAQsnB,EACjB,IAAK,IAAIl9B,EAAI,EAAGC,EAAI2V,EAAKlU,OAAS,EAAG1B,EAAI4V,EAAKlU,OAAQzB,EAAID,IAAK,CAC7D,MAAOo9B,EAAIC,GAAMznB,EAAK5V,IACfs9B,EAAIC,GAAM3nB,EAAK3V,GAClBo9B,EAAKvnB,GAAMynB,EAAKznB,GAAKD,GAAMynB,EAAKF,IAAOtnB,EAAIunB,IAAQE,EAAKF,GAAMD,IAAID,GAAQA,EAChF,CAEF,OAAOA,CACT,CA2BO,SAASK,GAAUC,EAAgBC,GACxC,OAAOC,EAAAA,GAAAA,IAAat/B,KAAK8S,IAAIssB,EAAQC,EAAOnoB,KAAM,CAAEqoB,KAAM,cAAeF,GAC3E,CCxJA,MAAMG,GAAU,KA4BT,SAASC,GAAU,G,IAAA,UAAE3M,EAAS,WAAE9B,EAAU,QAAEnb,EAAO,SAAEI,EAAQ,IAAE0R,GAA5C,EACxB,MAAM+X,GAAYC,EAAAA,EAAAA,QAA0B,OACrC7D,EAAO8D,IAAY1iC,EAAAA,EAAAA,UAAgB,CAAE4D,MAAO,WAEnD3D,EAAAA,EAAAA,WAAU,KACR,IAAIC,GAAM,EAkFV,OAjFAwiC,EAAS,CAAE9+B,MAAO,UACjB,WACC,MAAMwB,QAAY2wB,GAAiBH,GACnC,GAAI11B,EAAK,OACT,IAAKkF,EAEH,YADAs9B,EAAS,CAAE9+B,MAAO,SAGpB,MAAM++B,EDjBL,SAAkBC,EAA+BC,GACtD,MAAMC,EAAO,EAAchgC,KAAKqX,GAAM,IAChCO,EAAM5X,KAAK4X,KAAKooB,GAChBnoB,EAAM7X,KAAK6X,KAAKmoB,GAChBhc,EAAS8b,EAASjpB,OAClBopB,EAAKjc,EAAOkc,OAAO,CAAC33B,EAAGwO,IAAMxO,EAAIwO,EAAE,GAAI,GAAKiN,EAAO3gB,OACnD88B,EAAKnc,EAAOkc,OAAO,CAAC33B,EAAGwO,IAAMxO,EAAIwO,EAAE,GAAI,GAAKiN,EAAO3gB,OACnD+8B,EAASN,EAASh2B,IAAKvM,GAC3BA,EAAEuM,IAAK,I,IAAC0N,EAAGC,GAAE,EACX,MAAMC,EAAKF,EAAIyoB,EACTtoB,EAAKF,EAAI0oB,EACf,MAAO,CAACzoB,EAAKE,EAAMD,EAAKE,EAAKH,EAAKG,EAAMF,EAAKC,MAG3ChZ,EAAKwhC,EAAOvpB,OAAO/M,IAAKiN,GAAMA,EAAE,IAChCC,EAAKopB,EAAOvpB,OAAO/M,IAAKiN,GAAMA,EAAE,KAC/BspB,EAAIjZ,EAAIkZ,EAAIhZ,GAAM,CAACtnB,KAAK8S,OAAOlU,GAAKoB,KAAKkX,OAAOtY,GAAKoB,KAAK8S,OAAOkE,GAAKhX,KAAKkX,OAAOF,IAEnFupB,GAAMF,EAAKjZ,GAAM,EACjBoZ,GAAMF,EAAKhZ,GAAM,GAKhBrQ,EAAIE,IAJgBspB,EAIAD,EAJiC,CAC1DP,GADaS,EAIQH,GAHXvgC,KAAK4X,IAAIooB,GAAQS,EAAKzgC,KAAK6X,IAAImoB,GACzCG,EAAKO,EAAK1gC,KAAK6X,IAAImoB,GAAQS,EAAKzgC,KAAK4X,IAAIooB,KAF7B,IAACU,EAAYD,EAK3B,MAAO,CACL5B,OAAQuB,EAAOt2B,IAAKvM,GAAMA,EAAEuM,IAAK,I,IAAC0N,EAAGC,GAAE,E,MAAuB,CAACD,EAAI+oB,EAAI9oB,EAAI+oB,MAC3EnF,QAASjU,EAAKiZ,EACd/E,OAAQhU,EAAKgZ,EACbrpB,KACAE,KACA6oB,OAEJ,CChBoBW,EAlBW,aADbhqB,EAmBoBd,GAlBhBvP,KAAqBqQ,EAAKC,YAAcD,EAAKC,YAAYC,QAC/D/M,IAAKvM,GAAMA,EAAEuM,IAAK,I,IAAC40B,EAAK3W,GAAI,E,OFlCrC,SAAmB2W,EAAa3W,GACrC,OAAOiW,GAAY4C,QAAQ,CAAClC,EAAK3W,GACnC,CEgCiD8Y,CAAUnC,EAAK3W,MAiBhB9R,GACpC6qB,EAAajB,EAAMvE,OAASuE,EAAMxE,QAClCC,EAASt7B,KAAK4c,MAClB5c,KAAK8S,IA3CM,IA2CU9S,KAAKkX,IA5Cf,IA4C+BsoB,GAAUsB,EAAa,MAE7DC,EAAS/gC,KAAK8S,IAClB,KAAwB+sB,EAAMxE,SAC7BC,EAAS,IAAauE,EAAMvE,QAEzB0F,EDqBL,SACLnjC,EACAw9B,EACAC,EACAyF,G,IACAE,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAyD,CAACzpB,EAAGC,IAnFxD,SAAsBinB,EAAa3W,GAGxC,MAAO,CAFG,GAAc2W,EAAM1+B,KAAKqX,GAAM,IAC/BsnB,GAAa3+B,KAAKmtB,IAAIntB,KAAKkhC,IAAIlhC,KAAKqX,GAAK,EAAI,EAAOrX,KAAKqX,GAAM,MAE3E,CAgFI8pB,IAAgBlD,GAAUzmB,EAAGC,IAE/B,MAAM2oB,EAAS,CAACz9B,EAAWE,IAnB7B,SAAsBhF,EAAa6iC,EAAYD,GAC7C,MAAO,CACL5iC,EAAEoZ,GAAKypB,EAAK1gC,KAAK4X,IAAI/Z,EAAEmiC,MAAQS,EAAKzgC,KAAK6X,IAAIha,EAAEmiC,MAC/CniC,EAAEsZ,GAAKupB,EAAK1gC,KAAK6X,IAAIha,EAAEmiC,MAAQS,EAAKzgC,KAAK4X,IAAI/Z,EAAEmiC,MAEnD,CAeIoB,CAAavjC,GAAI8E,EAAI,GAAM04B,EAAU,GAAK0F,GAASzF,EAAS,EAAIz4B,EAAI,IAAOk+B,GACvEM,EAAMJ,KAAcb,EAAO,EAAG,IAC9BkB,EAAML,KAAcb,EAAO,EAAG,IAC9BmB,EAAMN,KAAcb,EAAO,EAAG,IACpC,MAAO,CACLC,GAAIgB,EAAI,GACRf,GAAIe,EAAI,GACRG,IAAKF,EAAI,GAAKD,EAAI,GAClBI,IAAKH,EAAI,GAAKD,EAAI,GAClBK,IAAKH,EAAI,GAAKF,EAAI,GAClBM,IAAKJ,EAAI,GAAKF,EAAI,GAEtB,CC1CkBO,CAAkB/B,EAAOL,GAASlE,EAAQyF,IAC/CV,EAAIC,EAAIlZ,EAAIE,GD4ClB,SACL3lB,EACA05B,EACAC,GAEA,MAAMuG,EAAS,CACb,CAAC,EAAG,GACJ,CAACxG,EAAS,GACV,CAAC,EAAGC,GACJ,CAACD,EAASC,IACVxxB,IAAK,I,IAACnH,EAAGE,GAAE,E,MAAK,CAAClB,EAAE0+B,GAAK19B,EAAIhB,EAAE6/B,IAAM3+B,EAAIlB,EAAE+/B,IAAK//B,EAAE2+B,GAAK39B,EAAIhB,EAAE8/B,IAAM5+B,EAAIlB,EAAEggC,OACpE/iC,EAAKijC,EAAO/3B,IAAKiN,GAAMA,EAAE,IACzBC,EAAK6qB,EAAO/3B,IAAKiN,GAAMA,EAAE,IAC/B,MAAO,CAAC/W,KAAK8S,OAAOlU,GAAKoB,KAAK8S,OAAOkE,GAAKhX,KAAKkX,OAAOtY,GAAKoB,KAAKkX,OAAOF,GACzE,CC1D+B8qB,CAAcd,EAAKxB,GAASlE,GAE/CyG,EAAS/hC,KAAKgiC,MAAMhB,EAAIQ,IAAKR,EAAIS,KACjCQ,EAAKjiC,KAAK8S,IAlDF,KAkDmB9S,KAAKkiC,MAAM9a,EAAKiZ,GAAM0B,IACjDI,EAAKniC,KAAK8S,IAnDF,KAmDmB9S,KAAKkiC,MAAM5a,EAAKgZ,GAAMyB,IACjDK,QAAgBhH,GAAY94B,EAAK,CAAC+9B,EAAIC,EAAIlZ,EAAIE,GAAK2a,EAAIE,GAlCnE,IAAkBxrB,EAmCZ,GAAIvZ,EAAK,OAGT,MAAMilC,EAAU,IAAIC,aAAa9C,GAAUlE,GAAQ3iB,KAAK+iB,KACxD,IAAK,IAAI74B,EAAI,EAAGA,EAAIy4B,EAAQz4B,IAAK,CAC/B,MAAM49B,GAAMnF,EAAS,EAAIz4B,EAAI,IAAOk+B,EACpC,IAAK,IAAIp+B,EAAI,EAAGA,EAAI68B,GAAS78B,IAAK,CAChC,MAAM+9B,GAAM/9B,EAAI,GAAM68B,KAAeuB,EACrC,IAAKnC,GAAOiB,EAAMhB,OAAQ6B,EAAID,GAAK,SACnC,MAAMR,EAAKe,EAAIX,GAAK19B,EAAIq+B,EAAIQ,IAAM3+B,EAAIm+B,EAAIU,IACpCvB,EAAKa,EAAIV,GAAK39B,EAAIq+B,EAAIS,IAAM5+B,EAAIm+B,EAAIW,IACpCY,EAAMviC,KAAKiZ,OAAQgnB,EAAKI,IAAOjZ,EAAKiZ,GAAO4B,GAC3CO,EAAMxiC,KAAKiZ,OAAQqO,EAAK6Y,IAAO7Y,EAAKgZ,GAAO6B,GAC7CI,EAAM,GAAKA,GAAON,GAAMO,EAAM,GAAKA,GAAOL,IAC9CE,EAAQx/B,EAAI28B,GAAU78B,GAAKy/B,EAAQI,EAAMP,EAAKM,GAChD,CACF,CACA,MAAMlD,EDuDL,SAAqBgD,GAC1B,MAAMI,EAAmB,GACzB,IAAK,IAAI9/B,EAAI,EAAGA,EAAI0/B,EAAQh/B,OAAQV,IAC9BiB,OAAOi4B,SAASwG,EAAQ1/B,KAAK8/B,EAAOhgC,KAAK4/B,EAAQ1/B,IAEvD,GAAsB,IAAlB8/B,EAAOp/B,OAAc,OAAO,KAChCo/B,EAAOvgC,KAAK,CAAC6U,EAAG2rB,IAAM3rB,EAAI2rB,GAC1B,MAAMC,EAAYD,GAChBD,EAAOziC,KAAK8S,IAAI2vB,EAAOp/B,OAAS,EAAGrD,KAAKiZ,MAAMypB,EAAID,EAAOp/B,UAC3D,IAAIyP,EAAM9S,KAAKiZ,MAAuB,EAAjB0pB,EAAS,MAAa,EACvCzrB,EAAMlX,KAAKkiC,KAAsB,EAAjBS,EAAS,MAAa,EAC1C,GAAIzrB,EAAMpE,EAAM,EAAG,CACjB,MAAM8vB,GAAU9vB,EAAMoE,GAAO,EAC7BpE,EAAM8vB,EAAS,GACf1rB,EAAM0rB,EAAS,EACjB,CACA,MAAO,CAAE9vB,MAAKoE,MAChB,CCxEqB2rB,CAAYR,GACrB1N,EAAS+K,EAAUoD,QACzB,IAAKzD,IAAW1K,EAEd,YADAiL,EAAS,CAAE9+B,MAAO,SAIpB6zB,EAAOte,MAAQmpB,GACf7K,EAAOre,OAASglB,EAChB,MAAM7lB,EAAMkf,EAAOG,WAAW,MAC9B,IAAKrf,EAAK,OACV,MAAM6mB,EAAQ7mB,EAAIstB,gBAAgBvD,GAASlE,GAC3C,IAAK,IAAIvkB,EAAI,EAAGA,EAAIsrB,EAAQh/B,OAAQ0T,IAAK,CACvC,MAAM5S,EAAIk+B,EAAQtrB,GAClB,IAAKnT,OAAOi4B,SAAS13B,GAAI,SACzB,MAAO5G,EAAG4pB,EAAGvlB,GAAKu9B,GAAUh7B,EAAGk7B,GAC/B/C,EAAMttB,KAAK,EAAI+H,GAAKxZ,EACpB++B,EAAMttB,KAAK,EAAI+H,EAAI,GAAKoQ,EACxBmV,EAAMttB,KAAK,EAAI+H,EAAI,GAAKnV,EACxB06B,EAAMttB,KAAK,EAAI+H,EAAI,GAAK,GAC1B,CACAtB,EAAIsf,aAAauH,EAAO,EAAG,GAE3B7mB,EAAIutB,YAAc,UAClBvtB,EAAIwtB,UAAY,EAChB,IAAK,MAAM1rB,KAAQsoB,EAAMhB,OACvBppB,EAAIytB,YACJ3rB,EAAK/M,QAAQ,CAAC,EAAU8oB,K,IAAToN,EAAID,GAAG,EACpB,MAAMzZ,EAAKwY,IAAckB,EAAKK,EACxB9Z,EAAKqU,EAAS,EAAImF,EAAKM,EACnB,IAANzN,EAAS7d,EAAI0tB,OAAOnc,EAAIC,GACvBxR,EAAI2tB,OAAOpc,EAAIC,KAEtBxR,EAAI4tB,YACJ5tB,EAAImD,SAENgnB,EAAS,CAAE9+B,MAAO,QAASu+B,UAC5B,EA7EA,GA6EI1b,MAAM,KACJvmB,GAAKwiC,EAAS,CAAE9+B,MAAO,WAEvB,KACL1D,GAAM,IAEP,CAAC01B,EAAWjd,EAASI,IAExB,MAAMlV,EAAQiwB,EAAa,IAAI3mB,KAAK,GAAG2mB,cAAuBhW,mBAAmB,SAAW,KAE5F,OACE,UAACnB,MAAAA,CAAI3a,UAAU,Y,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,W,WACb,SAACy1B,SAAAA,CACCJ,IAAKmL,EACLxgC,UAA2B,UAAhB48B,EAAMh7B,MAAoB,gBAAkB,SACvD0V,KAAK,MACLC,aAAY,6BAA4B1V,EAAQ,OAAOA,IAAU,MAElD,UAAhB+6B,EAAMh7B,QACL,UAAC+Y,MAAAA,CAAI3a,UAAU,sE,UACI,UAAhB48B,EAAMh7B,OAAqB,gBACX,SAAhBg7B,EAAMh7B,OAAoB,4BACV,SAAhBg7B,EAAMh7B,OAAoB,2CAIhB,UAAhBg7B,EAAMh7B,QAAqB,SAACwiC,GAAAA,CAAQjE,OAAQvD,EAAMuD,OAAQ1X,IAAKA,EAAK5mB,MAAOA,MAGlF,CAEA,SAASuiC,GAAQ,G,IAAA,OACfjE,EAAM,IACN1X,EAAG,MACH5mB,GAHe,EASf,MACMwiC,EAAU33B,MAAM43B,KAAK,CAAEngC,OADb,GACgC,CAACogC,EAAGnQ,KAClD,MAAMnvB,EAAIk7B,EAAOvsB,IAAM,EAAM4wB,GAAiBrE,EAAOnoB,IAAMmoB,EAAOvsB,MAC3DvV,EAAG4pB,EAAGvlB,GAAKu9B,GAAUh7B,EAAGk7B,GAC/B,MAAO,OAAO9hC,KAAK4pB,KAAKvlB,MAAM5B,KAAK4c,MAAM,IAAO0W,EAAMoQ,QACrDp/B,KAAK,MACFq/B,EACG,MAAPhc,GAAeA,EAAM0X,EAAOvsB,KAAO6U,EAAM0X,EAAOnoB,IAC5C,KAAQyQ,EAAM0X,EAAOvsB,MAASusB,EAAOnoB,IAAMmoB,EAAOvsB,KAClD,KACN,OACE,UAAC+G,MAAAA,CAAI3a,UAAU,4E,WACb,UAAC+b,OAAAA,C,UAAK,iBAAela,EAAQ,OAAOA,IAAU,GAAG,iBACjD,UAACka,OAAAA,CAAK/b,UAAU,iC,WACd,SAAC+b,OAAAA,CAAK/b,UAAU,Q,UAASS,EAAAA,EAAAA,GAAM0/B,EAAOvsB,IAAK,MAC3C,SAACmI,OAAAA,CACC/b,UAAU,4CACVyK,MAAO,CAAEi6B,WAAY,6BAA6BL,M,SAEnC,MAAdI,IACC,SAAC1oB,OAAAA,CACC/b,UAAU,yCACVyK,MAAO,CAAEk6B,KAAM,GAAGF,MAClBxoB,MAAO,QAAOxb,EAAAA,EAAAA,GAAMgoB,EAAM,UAIhC,SAAC1M,OAAAA,CAAK/b,UAAU,Q,UAASS,EAAAA,EAAAA,GAAM0/B,EAAOnoB,IAAK,QAE9B,MAAdysB,IAAsB,UAAC1oB,OAAAA,C,UAAK,gBAAatb,EAAAA,EAAAA,GAAMgoB,EAAM,QAG5D,CCtMA,SAASmc,GAAQC,EAAc9d,GAC7B,OAAIA,GAAU,EAAU,MACjB,GAAGjmB,KAAK4c,MAAM,IAAOmnB,EAAQ9d,MACtC,CAEA,SAASlD,GAAGnjB,GACV,MAAO,GAAGI,KAAK4c,MAAMhd,GAAQM,eAAe,aAC9C,CAEA,SAASa,GAAM2C,GACb,OAAO,IAAI2G,KAAK3G,GAAKsX,mBAAmB,QAAS,CAC/C6H,IAAK,UACLH,MAAO,OACPC,KAAM,WAEV,CAQO,SAASqhB,GAAgB5nB,GAC9B,OAAOA,EAAM6nB,aAAan6B,IAAKlI,IAAO,CACpC6jB,KAAM7jB,EAAE6hB,KAAO,mBACfmb,OAAQh9B,EAAEsiC,UACVC,KAAMviC,EAAEwiC,QACRC,OAAkB,OAAVziC,EAAE6hB,MAEd,CAGO,SAAS6gB,GAAcC,GAC5B,OAAOA,EAASC,SAAS16B,IAAK26B,IAAO,CAAEhf,KAAMgf,EAAEroB,MAAOwiB,OAAQ6F,EAAErgB,UAAW+f,KAAM,IACnF,CAMO,SAASO,GAAajhB,EAAarH,GACxC,MAAM6J,EAAS7J,EAAM6nB,aAAa/D,OAAO,CAAC33B,EAAG3G,IAAM2G,EAAI3G,EAAEsiC,UAAYtiC,EAAEwiC,QAAS,GAC1EO,EAAQvoB,EAAM6nB,aAAa3iC,KAAMM,GAAMA,EAAE6hB,MAAQA,GACjD4gB,EAASjoB,EAAM6nB,aAAa3iC,KAAMM,GAAgB,OAAVA,EAAE6hB,KAC1CmhB,EAAS3e,GAAU0e,EAAQA,EAAMT,UAAYS,EAAMP,QAAU,IAAMC,GAAQH,WAAa,GACxFlgC,EAAQ,CACZ,GAAG8/B,GAAQa,GAAOT,WAAa,EAAGje,SAAcxC,OAC5CkhB,GAASA,EAAMP,QAAU,EAAI,CAAC,GAAGN,GAAQa,EAAMP,QAASne,uBAA8B,GAC1F,GAAG6d,GAAQc,EAAQ3e,gCACfoe,EAAS,CAAC,GAAGP,GAAQO,EAAOH,UAAWje,uBAA8B,IAErE4e,EAAU7gC,EAAM8N,MACtB,MAAO,0BAA0B2R,SAAWzf,EAAMM,KAAK,YAAYugC,IACrE,CAUO,SAASC,GAAc,G,IAAA,KAAEjpB,GAAF,EAC5B,MAAM,KAAE7M,EAAI,MAAEmU,EAAK,KAAEC,GC9DhB,SAAuBvH,GAC5B,MAAO7M,EAAMqU,IAAWnmB,EAAAA,EAAAA,UAAoC,OACrDimB,EAAOG,IAAYpmB,EAAAA,EAAAA,WAAS,IAC5BkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,WAAS,GA4BjC,OA1BAC,EAAAA,EAAAA,WAAU,KAGR,GAFAkmB,EAAQ,MACRE,GAAQ,IACH1H,EAEH,YADAyH,GAAS,GAGX,IAAIE,GAAa,EnBgByB,IAAuC5Y,EmBFjF,OAbA0Y,GAAS,InBewE1Y,EmBblD,CAAEsB,MAAO,CAAEuX,IAAK5H,KnBcvCjR,EAAQoD,QAAUA,GAAQrG,IAAoE,CAClGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,iCACFsI,KmBvBJhN,KAAM,I,IAAEoR,KAAM0U,GAAU,EAClBF,GAAYH,EAAQK,GAAY,QAEtCC,MAAM,KACAH,GAAYD,GAAQ,KAE1BK,QAAQ,KACFJ,GAAYF,GAAS,KAGvB,KACLE,GAAa,IAEd,CAAC3H,IAEG,CAAE7M,OAAMmU,QAAOC,OACxB,CD8BgC2hB,CAAclpB,GAE5C,OAAIuH,GAAa,SAACwB,GAAOA,C,SAAC,wCACtBzB,IAAUnU,GAAa,SAAC4V,GAAOA,C,SAAC,uBAC/B5V,EACAA,EAAKg2B,SAOH,SAACC,GAAAA,CAAWj2B,KAAMA,KALrB,SAAC4V,GAAOA,C,SAAC,4FAHK,IASpB,CAEA,SAASqgB,GAAW,G,IAAA,KAAEj2B,GAAF,EAClB,MAAMyT,EACJzT,EAAKk2B,KAAOl2B,EAAKm2B,IAAM,GAAGpkC,GAAMiO,EAAKk2B,mBAhErBxhC,EAgEmDsL,EAAKm2B,IA/DnEpkC,GAAM,IAAIsJ,KAAK,IAAIA,KAAK3G,GAAKgb,UAAY,OAAkBpU,iBA+DiB,GAhErF,IAAoB5G,EAiElB,MAAM,MAAE0Y,EAAK,SAAEmoB,GAAav1B,EAE5B,OAAKoN,GAAO6nB,aAAa5gC,QAAWkhC,GAUlC,UAAC1qB,MAAAA,CAAI3a,UAAU,oB,UACZkd,GAASA,EAAM6nB,aAAa5gC,OAAS,IACpC,UAAC+hC,UAAAA,CAAQlmC,UAAU,Y,WACjB,UAACmmC,KAAAA,CAAGnmC,UAAU,sB,UAAsB,kBAAgB8P,EAAKyU,QACzD,UAAC1M,IAAAA,CAAE7X,UAAU,gC,UACVwlC,GAAa11B,EAAKyU,IAAKrH,GAAO,SAAOqG,EAAQ,KAAGrG,EAAMkpB,iBAAiB,UAAQ,IAC/ElpB,EAAMiI,OAAO,iCAA+BtB,GAAG3G,EAAMmpB,qBAAqB,SAE7E,SAACC,GAAAA,CAAUC,MAAOzB,GAAgB5nB,QAGrCmoB,IACC,UAACa,UAAAA,CAAQlmC,UAAU,Y,WACjB,UAACmmC,KAAAA,CAAGnmC,UAAU,sB,UAAsB,uBAAqB8P,EAAKyU,QAC9D,UAAC1M,IAAAA,CAAE7X,UAAU,gC,UAAgC,wBACrB6jB,GAAGwhB,EAASngB,WAAW,6BAA2B,IACvE0f,GACCS,EAASC,SACNrjC,OAAQsjC,GAAMA,EAAEroB,QAAUpN,EAAKyU,KAC/Byc,OAAO,CAAC33B,EAAGk8B,IAAMl8B,EAAIk8B,EAAErgB,UAAW,GACrCmgB,EAASngB,WACR,IAAI,6BAGT,SAACohB,GAAAA,CAAUC,MAAOnB,GAAcC,SAGpC,UAACxtB,IAAAA,CAAE7X,UAAU,gC,UAAgC,4JAEgC,IAC1Ec,KAAK4c,MAAM5N,EAAK02B,QAAQ,oHAtC3B,UAAC9gB,GAAOA,C,UAAC,OACFnC,EAAQ,oDAAkDzT,EAAKyU,IAAI,kDA0ChF,CAEA,SAAS+hB,GAAU,G,IAAA,MAAEC,GAAF,EACjB,MAAMxf,EAASwf,EAAMvF,OAAO,CAAC33B,EAAGhL,IAAMgL,EAAIhL,EAAEqhC,OAASrhC,EAAE4mC,KAAM,GAC7D,OACE,SAACwB,KAAAA,CAAGzmC,UAAU,Y,SACXumC,EAAM37B,IAAK04B,IACV,UAACoD,KAAAA,CAAkB1mC,UAAU,4D,WAC3B,SAAC+b,OAAAA,CAAK/b,UAAU,W,SAAYsjC,EAAI/c,QAChC,UAACxK,OAAAA,CAAK/b,UAAU,+C,WACd,SAAC+b,OAAAA,CACCtR,MAAO,CACL0M,MAAU,IAAOmsB,EAAI5D,OAAU5+B,KAAKkX,IAAI+O,EAAQ,GAAzC,IACP2d,WAAYpB,EAAI6B,OAnJZ,UAFN,cAwJF,SAACppB,OAAAA,CACCE,MAAM,aACNxR,MAAO,CAAE0M,MAAU,IAAOmsB,EAAI2B,KAAQnkC,KAAKkX,IAAI+O,EAAQ,GAAvC,IAA8C2d,WAzJ1D,iBA4JR,SAAC3oB,OAAAA,CAAK/b,UAAU,0B,SAA2B4kC,GAAQtB,EAAI5D,OAAS4D,EAAI2B,KAAMle,OAdnEuc,EAAI/c,QAmBrB,CAEA,SAASb,GAAQ,G,IAAA,SAAEpQ,GAAF,EACf,OACE,SAACqF,MAAAA,CAAI3a,UAAU,2F,SACZsV,GAGP,CE1KA,SAASqxB,GAAK,G,IAAA,UAAE3mC,KAAcE,GAAhB,EACZ,OACE,SAACya,MAAAA,CACCta,YAAU,OACVL,WAAWQ,EAAAA,EAAAA,IACT,oFACAR,MAEEE,GAGV,CAEA,SAAS0mC,GAAW,G,IAAA,UAAE5mC,KAAcE,GAAhB,EAClB,OACE,SAACya,MAAAA,CACCta,YAAU,cACVL,WAAWQ,EAAAA,EAAAA,IACT,2JACAR,MAEEE,GAGV,CAEA,SAAS2mC,GAAU,G,IAAA,UAAE7mC,KAAcE,GAAhB,EACjB,OACE,SAACya,MAAAA,CACCta,YAAU,aACVL,WAAWQ,EAAAA,EAAAA,IAAG,6BAA8BR,MACxCE,GAGV,CAsBA,SAAS4mC,GAAY,G,IAAA,UAAE9mC,KAAcE,GAAhB,EACnB,OAAO,SAACya,MAAAA,CAAIta,YAAU,eAAeL,WAAWQ,EAAAA,EAAAA,IAAG,OAAQR,MAAgBE,GAC7E,CCjDO,MAAM6mC,GACC,CAAC,IAAK,KADPA,GAGC,CAAC,EAAG,KAHLA,GAKG,CAAC,EAAG,IAGPC,GAAgB,CAAC,QAAS,QAAS,OAIzC,SAASC,GACdv8B,EACAw8B,GAEA,GAAa,MAATx8B,EAAe,MAAO,QAC1B,MAAOy8B,EAAOC,GAAOF,EACrB,OAAIx8B,GAAS08B,EAAY,MACrB18B,GAASy8B,EAAc,QACpB,OACT,CAMO,MAAME,GAAmD,CAC9DC,MAAO,iCACPH,MAAO,6BACPC,IAAK,4BCRA,SAASG,GAAe,G,IAAA,KAC7B5qB,EAAI,QACJhG,EAAO,SACPI,EAAQ,OACRywB,EAAM,cACNC,EAAa,OACbC,EAAM,UACNC,GAP6B,EAS7B,MAAMtqB,EAAKoqB,EAEX,OAAKpqB,GAgBH,UAAC6oB,UAAAA,CAAQlmC,UAAU,kD,WAEjB,UAAC4nC,KAAAA,CAAG5nC,UAAU,wB,UAAwB,OAAK2c,MAC3C,UAAChC,MAAAA,CAAI3a,UAAU,gC,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,8B,SAA8B,+BAC7Cqd,EAAGR,cACF,sB,UACG,cACA,IAAI1R,KAAK,GAAGkS,EAAGR,wBAAwBf,mBAAmB,YAGhD,MAAd0rB,EAAO/e,KAAe,UAAU+e,EAAO/e,IAAIlP,QAAQ,KACrC,MAAdiuB,EAAO7e,KAAe,WAAW6e,EAAO7e,IAAIpP,QAAQ,SAIvD,UAACoB,MAAAA,CAAI3a,UAAU,yB,WACb,SAAC6nC,GAAAA,CACC3qB,MAAM,eACNxS,MAAO2S,EAAGG,oBACV5B,MAAOyB,EAAGY,0BACVpC,YAAawB,EAAGa,qBAChB4pB,IAAwB,MAAnBzqB,EAAGoB,aAAuB,YAAYpB,EAAGoB,aAAalF,QAAQ,aAAU3N,EAC7EwO,MAAO6sB,GAAY5pB,EAAGG,qBAAuB,KAAMupB,OAErD,SAACc,GAAAA,CACC3qB,MAAM,gBACNxS,MAAO2S,EAAGc,oBACVvC,MAAOyB,EAAGe,0BACVvC,YAAawB,EAAGa,qBAChB9D,MAAO6sB,GAAY5pB,EAAGc,qBAAuB,KAAM4oB,UAKvD,UAACJ,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,SAACC,GAASA,CAAC7mC,UAAU,U,SAAU,cAEjC,SAAC8mC,GAAWA,C,UACV,SAACvG,GAASA,CACR3M,UAAWvW,EAAGla,GACd2uB,WAAYzU,EAAGR,YACflG,QAASA,EACTI,SAAUA,EACV0R,IAAK+e,EAAO/e,YAIlB,UAACke,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,SAACC,GAASA,CAAC7mC,UAAU,U,SAAU,8BAEjC,SAAC8mC,GAAWA,C,UACV,SAAC/d,GAAeA,CACdC,QAAS,EAAI+e,uBAAyD,GACtE9e,gBAAiBue,EACjBrwB,MAAO,IACPC,OAAQ,WAMbswB,GAKD,UAAC/sB,MAAAA,CAAI3a,UAAU,oE,WACb,UAAC2mC,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,SAACC,GAASA,CAAC7mC,UAAU,U,SAAU,8BAEjC,SAAC8mC,GAAWA,C,UACV,SAAC/iB,GAAqBA,CAACpH,KAAMA,UAGjC,UAACgqB,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,SAACC,GAASA,CAAC7mC,UAAU,U,SAAU,gCAEjC,SAAC8mC,GAAWA,C,UACV,SAAClB,GAAaA,CAACjpB,KAAMA,YAM1BgrB,GAGD,UAAChtB,MAAAA,CAAI3a,UAAU,a,WACb,SAACD,EAAAA,EAAMA,CAACE,SAAO,EAACf,QAAQ,UAAUO,KAAK,K,UACrC,UAACuoC,EAAAA,GAAIA,CAACC,GAAI,kBAAkBtrB,I,WAC1B,SAACurB,GAAAA,EAAMA,CAACloC,UAAU,iBAAiB,wBAIvC,SAACD,EAAAA,EAAMA,CAACE,SAAO,EAACf,QAAQ,UAAUO,KAAK,K,UACrC,UAACuoC,EAAAA,GAAIA,CAACC,GAAI,kBAAkBtrB,uB,WAC1B,SAACwrB,GAAAA,EAAKA,CAACnoC,UAAU,iBAAiB,qCAnHxC,UAAC2mC,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,UAACC,GAASA,C,UAAC,OAAKlqB,QAElB,SAACmqB,GAAWA,C,UACV,SAACjvB,IAAAA,CAAE7X,UAAU,gC,SAAgC,qDAqHvD,CAEA,SAAS6nC,GAAQ,G,IAAA,MACf3qB,EAAK,MACLxS,EAAK,MACLkR,EAAK,YACLC,EAAW,IACXisB,EAAG,MACH1tB,GANe,EAgBf,OACE,UAACO,MAAAA,CAAI3a,WAAWQ,EAAAA,EAAAA,IAAG,wBAAyB6mC,GAAoBjtB,I,WAC9D,SAACO,MAAAA,CAAI3a,UAAU,+B,SAAgCkd,KAC/C,SAACvC,MAAAA,CAAI3a,UAAU,sC,SACH,MAAT0K,EAAgB,IAAM5J,KAAK4c,MAAMhT,GAAO1J,eAAe,YAE1D,SAAC2a,EAAoBA,CACnBC,MAAOA,EACPC,YAAaA,EACb7b,UAAU,4BAEX8nC,IAAO,SAACntB,MAAAA,CAAI3a,UAAU,qB,SAAsB8nC,MAGnD,CCvLA,MAAMM,GAAkD,QA4BlDC,GAAiB,iDAOhB,SAASC,GAAU9pB,GACxB,OAAU,MAANA,EAAmB,WADoBE,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,IAAAA,UAAAA,GAErB2pB,GAClBlpB,GAAWX,GAAM,GAAW,iBAC5BW,GAAWX,GAAM,EAAU,eACxB,cACT,CAEA,SAAS+pB,GAAe/pB,EAAmBE,GACzC,OAAU,MAANF,GAAcE,EAAkB,wBAChCS,GAAWX,GAAM,GAAW,mBAC5BW,GAAWX,GAAM,EAAU,gBACxB,uBACT,CAEA,MAAMgqB,GAAU,CACd,CAAC,iBAAkB,wBACnB,CAAC,eAAgB,mBACjB,CAAC,eAAgB,aACjB,CAACH,GAAgB,sBAGnB,SAASI,GAAQjqB,GACf,OAAa,MAANA,EAAa,KAAM/d,EAAAA,EAAAA,GAAM+d,EAAI,EAAG,CAAE5d,WAAW,GACtD,CAEA,SAAS8nC,GAAU7mC,GACjB,OAAO,IAAIsJ,KAAK,GAAGtJ,cAAkBia,mBAAmB,QAAS,CAC/D0H,MAAO,OACPC,KAAM,WAEV,CAEA,SAASklB,GAAYxmC,GACnB,OAAsB,MAAfA,EAAEymC,WAAqBjqB,GAAmBxc,EAAEymC,UACrD,CAGA,SAASC,GAAY1mC,GACnB,GAAY,MAARA,EAAEqc,GAAY,MAAO,eACzB,MAAM9d,EAAS,GAAG+nC,GAAQtmC,EAAEqc,oBAC5B,OAAOmqB,GAAYxmC,GAAK,GAAGzB,cAAmBgoC,GAAUvmC,EAAEymC,aAAgBloC,CAC5E,CAaO,SAASooC,GAAW,G,IAAA,KAAEC,EAAI,IAAEC,EAAG,aAAE7sB,EAAY,SAAEC,GAA3B,EACzB,MAAM6sB,EAAW,CAAEC,oBAAqB,UAAUH,EAAK5kC,2BAIjDglC,EAAaJ,EAAKrlC,UAAWvB,GA/FM,OA+FAA,EAAEwa,MACrCysB,EAAWtoC,KAAKkX,IAAI,EAAGlX,KAAK8S,IAAIu1B,EAAYJ,EAAK5kC,OAAS,IAC1DklC,EAAoB,MAAPL,GAAeA,EAAIrsB,OAASR,EACzCmtB,EAAsB,MAAPN,GAAeL,GAAYK,GAEhD,OACE,UAAC9C,UAAAA,CACC3uB,aAAW,iCACXvX,UAAU,sD,WAEV,UAAC2a,MAAAA,CAAI3a,UAAU,sC,WACb,SAAC4nC,KAAAA,CAAG5nC,UAAU,wB,SAAwB,wCACtC,SAACymC,KAAAA,CAAGzmC,UAAU,0E,SACXwoC,GAAQ59B,IAAK,I,IAAC2+B,EAAOrsB,GAAM,E,OAC1B,UAACwpB,KAAAA,CAAe1mC,UAAU,4B,WACxB,SAAC+b,OAAAA,CAAKytB,eAAW,EAACxpC,WAAWQ,EAAAA,EAAAA,IAAG,yBAA0B+oC,KACzDrsB,IAFMA,WAUf,SAACvC,MAAAA,CAAI3a,UAAU,kB,UACb,UAAC2a,MAAAA,CAAI3a,UAAU,oC,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,2B,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,kD,SAAkD,UAClE,SAAC2a,MAAAA,CAAI3a,UAAU,gCAAgCyK,MAAOw+B,E,SACnDF,EAAKn+B,IAAKzI,IACT,MAAMsnC,EAAUtnC,EAAEwa,OAASR,EACrBuC,EAAYiqB,GAAYxmC,GAC9B,OACE,UAACunC,SAAAA,CAECtiC,KAAK,SACLuiC,eAAcF,EACdlyB,aAAY,GAAGpV,EAAEwa,SAASksB,GAAY1mC,KACtCyb,QAAS,IAAMxB,EAASja,EAAEwa,MAC1B3c,UAAU,kE,WAEV,SAAC+b,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IAGT,gCACAipC,EAAU,eAAiB,iB,SAG5BtnC,EAAEwa,QAEL,SAACZ,OAAAA,CACCC,cAAa,mBAAmB7Z,EAAEwa,OAClC3c,WAAWQ,EAAAA,EAAAA,IACT,iGACA8nC,GAAUnmC,EAAEqc,GAAIE,GAChB+qB,GAAW,yDAGf,SAAC1tB,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,kCACA+nC,GAAepmC,EAAEqc,GAAIE,I,SAGtB+pB,GAAQtmC,EAAEqc,QA/BRrc,EAAEwa,WAqCf,SAACZ,OAAAA,CAAK/b,UAAU,kE,SAAkE,iBAKnFgpC,IACC,UAACruB,MAAAA,CAAI3a,UAAU,e,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,kBAChB,SAAC2a,MAAAA,CAAI3a,UAAU,gCAAgCyK,MAAOw+B,E,UAGpD,UAACS,SAAAA,CACCtiC,KAAK,SACLuiC,eAAcN,EACd9xB,aAAY,GAAGyxB,EAAIrsB,2BAA2ByrB,OAAwBS,GAAYG,KAClFprB,QAAS,IAAMxB,EAAS4sB,EAAIrsB,MAC5BlS,MAAO,CAAEm/B,WAAY,GAAGR,UACxBppC,WAAWQ,EAAAA,EAAAA,IACT,8MACA6oC,EACI,gEACA,W,WAGN,SAACttB,OAAAA,CAAK/b,UAAU,sC,SAAuCgpC,EAAIrsB,QAC3D,UAACZ,OAAAA,CAAK/b,UAAU,W,UAAW,SAClBooC,GACNkB,GAAgB,cAAcZ,GAAUM,EAAIJ,sBAInD,SAAC7sB,OAAAA,CAAK/b,UAAU,gCAO9B,C,qCCzNA,MAAM6pC,GAAS,CAAExjC,MAAO,GAAIyjC,KAAM,SAgB5BC,GAAeC,EAAAA,cAA8C,MAYnE,SAASC,GAAe,G,IAAA,GACtB9mC,EAAE,UACFnD,EAAS,SACTsV,EAAQ,OACRhH,KACGpO,GALmB,EAUtB,MAAMgqC,EAAWF,EAAAA,QACXG,EAAU,SAAShnC,GAAM+mC,EAASl8B,QAAQ,KAAM,MAEtD,OACE,SAAC+7B,GAAa1zB,SAAQ,CAAC3L,MAAO,CAAE4D,U,UAC9B,UAACqM,MAAAA,CACCta,YAAU,QACV+pC,aAAYD,EACZnqC,WAAWQ,EAAAA,EAAAA,IACT,8pBACAR,MAEEE,E,WAEJ,SAACmqC,GAAAA,CAAWlnC,GAAIgnC,EAAS77B,OAAQA,KACjC,SAACg8B,GAAAA,EAAqC,C,SAAEh1B,QAIhD,CAEA,MAAM+0B,GAAc,I,IAAA,GAAElnC,EAAE,OAAEmL,GAA6C,EACrE,MAAMi8B,EAAcxgC,OAAOC,QAAQsE,GAAQrM,OAAQ,I,IAAC,CAAEqM,GAAO,E,OAAKA,EAAOk8B,OAASl8B,EAAO8L,QAEzF,OAAKmwB,EAAYpmC,QAKf,SAACsG,QAAAA,CACCggC,wBAAyB,CACvBC,OAAQ3gC,OAAOC,QAAQ6/B,IACpBj/B,IACE,I,IAAC4/B,EAAOG,GAAO,E,MAAK,KAC/BA,iBAAsBxnC,SACtBonC,EACC3/B,IAAK,I,IAAChD,EAAKgjC,GAAW,EACrB,MAAMxwB,EAAQwwB,EAAWJ,QAAQA,IAA2CI,EAAWxwB,MACvF,OAAOA,EAAQ,aAAaxS,MAAQwS,KAAW,OAEhDhV,KAAK,eAIGA,KAAK,SAnBL,MAyBLylC,GAAeP,GAAAA,EAErB,SAASQ,GAAoB,G,IAAA,OAC3B9nB,EAAM,QACN+nB,EAAO,UACP/qC,EAAS,UACTgrC,EAAY,MAAK,UACjBC,GAAY,EAAK,cACjBC,GAAgB,EAAK,MACrBhuB,EAAK,eACLiuB,EAAc,eACdC,EAAc,UACd9kB,EAAS,MACTlM,EAAK,QACLixB,EAAO,SACPC,GAb2B,EAsB3B,MAAM,OAAEh9B,GA9FV,WACE,MAAMqnB,EAAUqU,EAAAA,WAAiBD,IAEjC,IAAKpU,EACH,MAAM,IAAIzsB,MAAM,qDAGlB,OAAOysB,CACT,CAsFqB4V,GAEbC,EAAexB,EAAAA,QAAc,KACjC,GAAIiB,IAAcF,GAAS5mC,OACzB,OAAO,KAGT,MAAOsnC,GAAQV,EAETH,EAAac,GAA4Bp9B,EAAQm9B,EAD3C,GAAGH,GAAYG,GAAMzlB,SAAWylB,GAAMjhC,MAAQ,WAEpDE,EACH4gC,GAA6B,iBAAVpuB,EAEhB0tB,GAAY1tB,MADZ5O,EAAO4O,IAA+BA,OAASA,EAGrD,OAAIiuB,GAEA,SAACxwB,MAAAA,CAAI3a,WAAWQ,EAAAA,EAAAA,IAAG,cAAe4qC,G,SAAkBD,EAAezgC,EAAOqgC,KAIzErgC,GAIE,SAACiQ,MAAAA,CAAI3a,WAAWQ,EAAAA,EAAAA,IAAG,cAAe4qC,G,SAAkB1gC,IAHlD,MAIR,CAACwS,EAAOiuB,EAAgBJ,EAASE,EAAWG,EAAgB98B,EAAQg9B,IAEvE,IAAKtoB,IAAW+nB,GAAS5mC,OACvB,OAAO,KAGT,MAAMwnC,EAA+B,IAAnBZ,EAAQ5mC,QAA8B,QAAd6mC,EAE1C,OACE,UAACrwB,MAAAA,CACC3a,WAAWQ,EAAAA,EAAAA,IACT,yHACAR,G,UAGA2rC,EAA2B,KAAfH,GACd,SAAC7wB,MAAAA,CAAI3a,UAAU,e,SACZ+qC,EACE9oC,OAAQwpC,GAAuB,SAAdA,EAAKrkC,MACtBwD,IAAI,CAAC6gC,EAAM7R,KACV,MAAMhyB,EAAM,GAAGyjC,GAAWI,EAAKjhC,MAAQihC,EAAKzlB,SAAW,UACjD4kB,EAAac,GAA4Bp9B,EAAQm9B,EAAM7jC,GACvDgkC,EAAiBxxB,GAASqxB,EAAKV,QAAQtxB,MAAQgyB,EAAKrxB,MAE1D,OACE,SAACO,MAAAA,CAEC3a,WAAWQ,EAAAA,EAAAA,IACT,sGACc,QAAdwqC,GAAuB,gB,SAGxB1kB,QAA6B1a,IAAhB6/B,GAAM/gC,OAAuB+gC,EAAKjhC,KAC9C8b,EAAUmlB,EAAK/gC,MAAO+gC,EAAKjhC,KAAMihC,EAAM7R,EAAO6R,EAAKV,UAEnD,sB,UACGH,GAAY/qC,MACX,SAAC+qC,EAAW/qC,KAAI,KAEfqrC,IACC,SAACvwB,MAAAA,CACC3a,WAAWQ,EAAAA,EAAAA,IACT,iEACA,CACE,cAA6B,QAAdwqC,EACf,MAAqB,SAAdA,EACP,kDACgB,WAAdA,EACF,SAAUW,GAA2B,WAAdX,IAG3BvgC,MACE,CACE,aAAcmhC,EACd,iBAAkBA,MAM5B,UAACjxB,MAAAA,CACC3a,WAAWQ,EAAAA,EAAAA,IACT,2CACAmrC,EAAY,YAAc,gB,WAG5B,UAAChxB,MAAAA,CAAI3a,UAAU,e,UACZ2rC,EAAYH,EAAe,MAC5B,SAACzvB,OAAAA,CAAK/b,UAAU,wB,SACb4qC,GAAY1tB,OAASuuB,EAAKjhC,UAG9BihC,EAAK/gC,QACJ,SAACqR,OAAAA,CAAK/b,UAAU,qD,SACbyrC,EAAK/gC,MAAM1J,0BAhDjByqC,EAAKzlB,eA4D1B,CA6DA,SAAS0lB,GAA4Bp9B,EAAqBy8B,EAAkBnjC,GAC1E,GAAuB,iBAAZmjC,GAAoC,OAAZA,EACjC,OAGF,MAAMc,EACJ,YAAad,GAAsC,iBAApBA,EAAQA,SAA4C,OAApBA,EAAQA,QACnEA,EAAQA,aACRn/B,EAEN,IAAIkgC,EAAyBlkC,EAY7B,OAVIA,KAAOmjC,GAA2D,iBAAzCA,EAAQnjC,GACnCkkC,EAAiBf,EAAQnjC,GAEzBikC,GACAjkC,KAAOikC,GACuD,iBAAvDA,EAAejkC,KAEtBkkC,EAAiBD,EAAejkC,IAG3BkkC,KAAkBx9B,EAASA,EAAOw9B,GAAkBx9B,EAAO1G,EACpE,CAlFoB0iC,GAAAA,EC7Nb,MAAMyB,GAAW,SAGjB,SAASC,GAAaxnC,GAG3B,OAAO2G,KAAK/C,MAAM5D,EAAInC,MAAM,EAAG,IACjC,CAGA,MAAM4pC,GAAkB,CAAC,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,IAEvCC,GAAU,OAQhB,SAASC,GAAWC,GAClB,MAAMnnC,EAAI,IAAIkG,KAAKihC,GACnB,OAA4B,GAArBnnC,EAAEonC,iBAAwBpnC,EAAEqnC,aACrC,CAEA,SAASC,GAAc3S,GACrB,OAAOzuB,KAAKgmB,IAAIrwB,KAAKiZ,MAAM6f,EAAQ,IAAKA,EAAQ,GAAI,EACtD,CAGA,SAAS4S,GAAUC,GACjB,OAAQL,IACN,MAAMnnC,EAAI,IAAIkG,KAAKihC,GACnB,OAAIK,GAAe,GAAWhsB,OAAOxb,EAAEonC,kBAEhC,GADOpnC,EAAEjE,eAAe,QAAS,CAAEwiB,MAAO,QAASkpB,SAAU,QAAS1+B,QAAQ,IAAK,QACtEyS,OAAOxb,EAAEonC,kBAAkBhqC,MAAM,KAEzD,CAOO,SAASsqC,GAAOC,G,IAAkBC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAe,GACtD,MAAMtJ,EAASqJ,EAAO3qC,OAAQwqB,GAAM/nB,OAAOi4B,SAASlQ,IACpD,GAAsB,IAAlB8W,EAAOp/B,OACT,MAAO,CAAE2oC,OAAQ,CAAC,EAAG,GAAIC,MAAO,GAAIC,WAAYR,GAAU,IAE5D,MAAM54B,EAAM9S,KAAK8S,OAAO2vB,GAAU2I,GAC5Bl0B,EAAMlX,KAAKkX,OAAOurB,GAAU2I,GAC5Be,EAASd,GAAWv4B,IAAQ24B,GAAcJ,GAAWv4B,IAAQA,EAAM,EAAI,GACvE+xB,EAAUwG,GAAWn0B,GACrBk1B,EAAgBpsC,KAAKkX,IAAI2tB,EAAUsH,EAAS,EAAG,GAC/CnpB,EACJmoB,GAAgB7pC,KAAMiH,GAAMvI,KAAKkiC,KAAKkK,EAAgB7jC,IAAMwjC,IAC5DZ,GAAgBA,GAAgB9nC,OAAS,GAErC4oC,EAAkB,GACxB,IAAK,IAAItpC,EAAI3C,KAAKkiC,KAAKiK,EAASnpB,GAAQA,EAAMrgB,GAAKkiC,EAASliC,GAAKqgB,EAC/DipB,EAAMxpC,KAAKgpC,GAAc9oC,IAE3B,MAAO,CAAEqpC,OAAQ,CAACl5B,EAAKoE,GAAM+0B,QAAOC,WAAYR,GAAU1oB,GAC5D,CAGO,SAASqpB,GAAU3oC,GACxB,OAAO,IAAI2G,KAAK6gC,GAAaxnC,IAAMsX,mBAAmB,QAAS,CAC7D6H,IAAK,UACLH,MAAO,OACPC,KAAM,UACNipB,SAAU,OAEd,CAGO,SAASU,GACdt9B,EACAu9B,GAEA,OAAOv9B,EAAKlF,IAAKvM,IAAO,IAAKA,EAAG,CAAC0tC,IAAWC,GAAavrB,OAAOpiB,EAAEgvC,OACpE,CCzDA,SAASC,GAAY5iC,GACnB,MAAqB,iBAAVA,GAAuBhG,OAAOi4B,SAASjyB,GAC9C5J,KAAKC,IAAI2J,IAAU,IAAkB,IAAIA,EAAQ,KAAW6O,QAAQ,MACpEzY,KAAKC,IAAI2J,IAAU,IAAc,IAAIA,EAAQ,KAAO6O,QAAQ,MACzDzY,KAAK4c,MAAMhT,GAAO1J,eAAe,SAHyB,EAInE,CAOO,SAASusC,GAAiB,G,IAAA,KAAEz9B,EAAI,QAAEu9B,EAAO,MAAEv6B,EAAK,OAAE06B,EAAM,KAAEC,GAAhC,EAC/B,MAAMC,GAAc7lC,EAAAA,EAAAA,SAAqB,KACvC,MAAM8lC,EAAmB,CAAC,EAC1B,IAAK,MAAM16B,KAAQH,EACjB66B,EAAI16B,EAAK+S,SAAW,CAAE9I,MAAOjK,EAAKiK,MAAO9C,MAAOnH,EAAKmH,OAEvD,OAAOuzB,GACN,CAAC76B,IAIE86B,GAAW/lC,EAAAA,EAAAA,SAAQ,IAAMulC,GAAQt9B,EAAMu9B,GAAU,CAACv9B,EAAMu9B,IACxDQ,GAAKhmC,EAAAA,EAAAA,SAAQ,IAAM8kC,GAAOiB,EAAShjC,IAAKvM,GAAMA,EAAE0tC,MAAa,CAAC6B,IAEpE,OAAoB,IAAhB99B,EAAK3L,QAEL,SAACwW,MAAAA,CAAI3a,UAAU,0C,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,gC,SAAgC,8BAMjD,SAACiqC,GAAcA,CAAC37B,OAAQo/B,EAAa1tC,UAAU,gB,UAC7C,UAAC8tC,GAAAA,EAAaA,CAACh+B,KAAM89B,EAAU1kB,OAAQ,CAAE6kB,IAAK,GAAIC,MAAO,GAAIC,OAAQ,EAAGtJ,KAAM,I,WAC5E,SAAC9e,GAAAA,EAAaA,CAACC,gBAAgB,MAAM9lB,UAAU,mBAC/C,SAAC+lB,GAAAA,EAAKA,CACJC,QAAS+lB,GACT3kC,KAAK,SACL6R,MAAM,OACN6zB,OAAQe,EAAGf,OACXC,MAAOc,EAAGd,MACVmB,cAAeL,EAAGb,WAClBhtC,UAAU,aAEZ,SAACkmB,GAAAA,EAAKA,CACJgoB,cAAeZ,GACfpwB,MAAO,CACLxS,MAAO8iC,EACPrnB,OAAQ,GACRC,SAAU,aACV3b,MAAO,CAAE8d,WAAY,WAEvBvoB,UAAU,UACVmX,MAAO,MAET,SAAC0zB,GAAYA,CACXsD,SACE,SAACrD,GAAmBA,CAClBK,eAAgB,CAAC5G,EAAGwG,KAClB,MAAMlpC,EAAQkpC,IAAU,IAAIA,UAAUsC,GACtC,MAAwB,iBAAVxrC,EAAqBsrC,GAAUtrC,GAAS,IAExDykB,UAAY5b,GA1D1B,SAA4BA,EAAwB+iC,GAClD,MAAqB,iBAAV/iC,GAAuBhG,OAAOi4B,SAASjyB,GAC3C,GAAG5J,KAAK4c,MAAMhT,GAAO1J,eAAe,YAAYysC,IADU,GAEnE,CAuDoCW,CAAmB1jC,EAA0B+iC,OAIxE36B,EAAMlI,IAAKqI,IACV,SAACo7B,GAAAA,EAAIA,CAEHjnC,KAAK,WACL4e,QAAS/S,EAAK+S,QACdtM,OAAQzG,EAAKmH,MACbT,YAAa,EACbmM,gBAAiB7S,EAAKq7B,OAAS,WAAQ1iC,EACvC2iC,IAAKt7B,EAAKq7B,OAAS,CAAEjwC,EAAG,EAAGob,KAAM,qBAAwB,CAAEpb,EAAG,GAC9DmwC,UAAW,CAAEnwC,EAAG,GAChBowC,cAAY,GARPx7B,EAAK+S,cActB,C,yBCtDO,SAAS0oB,GAAe,G,IAAA,KAC7B5+B,EAAI,QACJu9B,EAAO,MACPv6B,EAAK,MACL67B,EAAK,gBACL1lB,EAAe,cACf2lB,GAAgB,EAAK,OACrBpB,EAAS,SAPoB,EAU7B,MAAMqB,GAAchnC,EAAAA,EAAAA,SAAQ,KAG1B,IAAI+L,EAAMioB,IACN7jB,GAAM,IACV,MAAM82B,EAAQ3sC,IACK,iBAANA,GAAkBuC,OAAOi4B,SAASx6B,KACvCA,EAAIyR,IAAKA,EAAMzR,GACfA,EAAI6V,IAAKA,EAAM7V,KAGvB,IAAK,MAAM4sC,KAAUj/B,EAAM,CACzB,IAAK,MAAMmD,KAAQH,EAAOg8B,EAAKC,EAAO97B,EAAK+S,UAC3C,GAAI2oB,EACF,IAAK,MAAMrpB,KAAQqpB,EACjBG,EAAKC,EAAOzpB,EAAK0pB,SACjBF,EAAKC,EAAOzpB,EAAK2pB,QAGvB,CACA,GAAIhmB,EACF,IAAK,MAAM9mB,KAAK4H,OAAOsB,OAAO4d,GAAkB6lB,EAAK3sC,GAEvD,IAAKuC,OAAOi4B,SAAS/oB,GAAM,MAAO,EAAE,EAAG,GACvC,MAAMs7B,EAAoB,IAAbl3B,EAAMpE,IAAc,EACjC,MAAO,CAACA,EAAMs7B,EAAKl3B,EAAMk3B,IACxB,CAACp/B,EAAMgD,EAAO67B,EAAO1lB,KAEjBkmB,EAASC,IAAcpxC,EAAAA,EAAAA,UAAkC,MAC1D8uC,EAASqC,GAAWN,GAG1B5wC,EAAAA,EAAAA,WAAU,IAAMmxC,EAAW,MAAO,CAACP,IAEnC,MAYMjB,GAAW/lC,EAAAA,EAAAA,SAAQ,IAAMulC,GAAQt9B,EAAMu9B,GAAU,CAACv9B,EAAMu9B,IACxDQ,GAAKhmC,EAAAA,EAAAA,SAAQ,IAAM8kC,GAAOiB,EAAShjC,IAAKvM,GAAMA,EAAE0tC,MAAa,CAAC6B,IAG9DF,GAAc7lC,EAAAA,EAAAA,SAAqB,KACvC,MAAM8lC,EAAmB,CAAC,EAC1B,IAAK,MAAM16B,KAAQH,EACjB66B,EAAI16B,EAAK+S,SAAW,CAAE9I,MAAOjK,EAAKiK,MAAO9C,MAAOnH,EAAKmH,OAEvD,OAAOuzB,GACN,CAAC76B,IAEJ,OAAoB,IAAhBhD,EAAK3L,QAEL,SAACwW,MAAAA,CAAI3a,UAAU,0C,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,gC,SAAgC,8BAMjD,SAAC2a,MAAAA,CAAI3a,UAAU,gBAAgBqvC,QAjChBl/B,IACfA,EAAEm/B,iBACF,MAAO3lB,EAAM4lB,GAAQzC,EACf0C,EAAQD,EAAO5lB,EACf/N,EAAQzL,EAAEs/B,OAAS,EAtDT,OAuDVC,EAAS/lB,EAAO6lB,EAAQ5zB,EACxB+zB,EAASJ,EAAOC,EAAQ5zB,EAC1B+zB,EAASD,EAAS,IAAKN,EAAW,CAACM,EAAQC,K,UA2B7C,SAAC1F,GAAcA,CAAC37B,OAAQo/B,EAAa1tC,UAAU,gB,UAC7C,UAAC8tC,GAAAA,EAAaA,CAACh+B,KAAM89B,EAAU1kB,OAAQ,CAAE6kB,IAAK,GAAIC,MAAO,GAAIC,OAAQ,EAAGtJ,KAAM,I,WAC5E,SAAC3a,OAAAA,C,SACE4kB,IACC,UAAC3kB,iBAAAA,CAAe9mB,GAAG,oBAAoB+kB,GAAG,IAAIE,GAAG,IAAID,GAAG,IAAIE,GAAG,I,WAC7D,SAAC6B,OAAAA,CAAKC,OAAO,KAAKC,UAAU,UAAUC,YAAa,OACnD,SAACH,OAAAA,CAAKC,OAAO,OAAOC,UAAU,UAAUC,YAAa,YAK3D,SAACxE,GAAAA,EAAaA,CAACC,gBAAgB,QAG9B8oB,IACC,SAACgB,GAAAA,EAAaA,CACZxnB,GAAI0kB,EAAO,GACXzkB,GAAI,EACJ5O,KAAK,0BACLc,YAAa,EACbs1B,WAAW,YAIf,SAAC9pB,GAAAA,EAAKA,CACJC,QAAS+lB,GACT3kC,KAAK,SACL6R,MAAM,OACN6zB,OAAQe,EAAGf,OACXC,MAAOc,EAAGd,MACVmB,cAAeL,EAAGb,WAClB/mB,SAAU,MAEZ,SAACC,GAAAA,EAAKA,CACJ4mB,OAAQA,EACRoB,cAAgB/rC,IAAc1B,EAAAA,EAAAA,GAAM0B,EAAG,GACvC8jB,SAAU,GACV/I,MAAO,CAAExS,MAAO8iC,EAAQrnB,OAAQ,GAAIC,SAAU,aAAc3b,MAAO,CAAEwb,SAAU,QAGjF,SAAC4kB,GAAYA,CACXsD,SACE,SAACrD,GAAmBA,CAClBxkB,UAAW,CAAC5b,EAAOF,IAEV,GADOkjC,EAAYljC,IAAiB0S,OAAS1S,OAChC/J,EAAAA,EAAAA,GAAMiK,EAAiB,MAAM8iC,IAEnDrC,eAAgB,CAAC5G,EAAGwG,KAClB,MAAMlpC,EAAQkpC,IAAU,IAAIA,UAAUsC,GACtC,MAAwB,iBAAVxrC,EAAqBsrC,GAAUtrC,GAAS,QAO7D8sC,GAAO/jC,IAAK0a,IACX,SAAC+oB,GAAAA,EAAIA,CAEHjnC,KAAK,WACL4e,QAASV,EAAK2pB,OACdv1B,OAAQ4L,EAAKlL,MACbT,YAAa,EACbmM,gBAAgB,MAChBgqB,cAAe,GACfvB,KAAK,EACLE,cAAc,EACd/nB,mBAAmB,GATd,GAAGpB,EAAK2pB,gBAYhBN,GAAO/jC,IAAK0a,IACX,SAAC+oB,GAAAA,EAAIA,CAEHjnC,KAAK,WACL4e,QAASV,EAAK0pB,OACdt1B,OAAQ4L,EAAKlL,MACbT,YAAa,EACbmM,gBAAgB,MAChBgqB,cAAe,GACfvB,KAAK,EACLE,cAAc,EACd/nB,mBAAmB,GATd,GAAGpB,EAAK0pB,gBAchBl8B,EAAMlI,IAAKqI,IACV,SAACo7B,GAAAA,EAAIA,CAEHjnC,KAAK,WACL4e,QAAS/S,EAAK+S,QACdtM,OAAQzG,EAAKmH,MACbT,YAAa,EACb40B,IAAK,CAAElwC,EAAG,EAAGob,KAAMxG,EAAKmH,OACxBq0B,cAAc,EACd/nB,mBAAmB,GAPdzT,EAAK+S,UAYb4oB,IACC,SAACmB,GAAAA,EAAaA,CACZx3B,EAAG,EACHmB,OAAQoP,GACRnP,YAAa,IACbmM,gBAAgB,MAChB5I,MAAO,CAAExS,MAAO,MAAO0b,SAAU,QAAS3M,KAAMqP,GAAW7C,SAAU,IACrE4pB,WAAW,WAKd5mB,GACC,OACSjf,QAAQif,GAIfre,IACC,I,IAAChD,EAAK8C,GAAM,E,OACF,MAATA,IACE,SAACqlC,GAAAA,EAAaA,CAEZx3B,EAAG7N,EACHgP,OAAQ8O,GAAiB5gB,GACzB+R,YAAa,IACbmM,gBAAgB,MAChB5I,MAAO,CACLxS,MAAOke,GAAiBhhB,GACxBwe,SAAU,QACV3M,KAAM+O,GAAiB5gB,GACvBqe,SAAU,IAEZ4pB,WAAW,UAXNjoC,WAmBzB,CCxRO,SAASooC,KACd,MAAO9Q,EAAS+Q,IAAcjyC,EAAAA,EAAAA,UAA2C,OAClEk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChC8L,EAAOomC,IAAYlyC,EAAAA,EAAAA,UAAwB,MAgClD,OA9BAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAwBhB,MAtBcnK,WACZ,IACE,MAAM,KAAE0R,S5B6E0DpE,E4B7EvB,CAAEsB,MAAO,CAAEwlB,MAAO,O5B8EzD9mB,GAASoD,QAAUA,GAAQrG,IAAyD,CACxFuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,wBACFsI,K4BvFH,GAAInD,EAAW,OAEf,MAAM4nC,EAAKrgC,GAAM+iB,QACjB,IAAKsd,GAAIhR,UAAUh7B,OAEjB,YADA8rC,EAAW,MAKb,MAAMG,EAAcnR,GAAiBkR,GACrCF,EAAWG,EACb,CAAE,MACK7nC,GAAW2nC,EAAS,2CAC3B,CAAE,QACK3nC,GAAW4qB,GAAW,EAC7B,C5B6D6B,IAAuCznB,G4B1DtEpN,GACO,KACLiK,GAAY,IAEb,IAEI,CAAE22B,UAAShM,UAASppB,QAC7B,CCzBO,SAASumC,K,IAAkBC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAqB,EACrD,MAAO1vB,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChCkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,WAAS,GA4CjC,OA1CAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EA4BhB,OA3BA4qB,GAAW,GACX9O,GAAQ,GAERpB,GAAgB,CACdjW,MAAO,CACL7D,OAAQ,QACRkpB,gBAAgB,EAChBG,MAAO,OAKR9zB,KAAM,I,IAAA,KAAEoR,EAAI,MAAEhG,GAAO,EAChBvB,IACJ0qB,EAAYnjB,GAAM+iB,SAAW,SACfjnB,IAAV9B,GAAqBua,GAAQ,MAElCI,MAAM,KACAlc,IACH0qB,EAAY,IACZ5O,GAAQ,MAGXK,QAAQ,KACFnc,GAAW4qB,GAAW,KAGxB,KACL5qB,GAAY,IAEb,CAAC+nC,IAUG,CAAEC,aARW1oC,EAAAA,EAAAA,SAAQ,KAC1B,MAAM+C,EAAM,IAAI9D,IAChB,IAAK,MAAM7B,KAAK2b,EACdhW,EAAInB,IAAIxE,EAAEgc,eAAgBhc,GAE5B,OAAO2F,GACN,CAACgW,IAEkBsS,UAAShP,OACjC,CCLA,MAAMssB,GAAc,CAClB,CAAC,aAAc,uBACf,CAAC,cAAe,wBAChB,CAAC,aAAc,uBACf,CAAC,cAAe,wBAChB,CAAC,aAAc,uBACf,CAAC,cAAe,yBC7ClB,GAVoB,I,IAAA,MAAEv0B,EAAK,YAAEw0B,EAAW,SAAEn7B,GAA2B,E,OACnE,UAAC5G,SAAAA,CAAO1O,UAAU,6C,WAChB,UAAC2a,MAAAA,CAAI3a,UAAU,iB,WACb,SAAC0wC,KAAAA,CAAG1wC,UAAU,wC,SAAyCic,IACtDw0B,GAAc,SAAC54B,IAAAA,CAAE7X,UAAU,qC,SAAsCywC,IAAmB,QAEtFn7B,GAAW,SAACqF,MAAAA,CAAI3a,UAAU,mC,SAAoCsV,IAAkB,SCArF,GARqB,I,IAAA,MAAE2G,EAAK,YAAEw0B,EAAW,SAAEn7B,GAA4B,E,OACrE,UAAC4wB,UAAAA,CAAQlmC,UAAU,gC,WACjB,SAAC4nC,KAAAA,CAAG5nC,UAAU,wB,SAAyBic,IACtCw0B,GAAc,SAAC54B,IAAAA,CAAE7X,UAAU,qC,SAAsCywC,IAAmB,KACpFn7B,GAAW,SAACqF,MAAAA,CAAI3a,UAAU,O,SAAQsV,IAAkB,SCcnDq7B,GAA2C,CAC/CC,WAAY,sBACZC,gBAAiB,sBACjBC,KAAM,aACNtB,MAAO,gBAGHuB,GAAiD,CACrD9rB,OAAQ,cACR+rB,QAAS,kBAGLC,GAAY,UAaH,SAASC,KACtB,MAAM,QAAEhS,EAAShM,QAASie,GAAenB,MAClC3P,EAAM+Q,IAAWpzC,EAAAA,EAAAA,UAAsB,eACvCqzC,EAAYC,IAAiBtzC,EAAAA,EAAAA,UAAqB,WAGnD,YAAEuyC,EAAard,QAASqe,GAAoBlB,GAAkB,IAG7DmB,IAAgBC,EAAAA,EAAAA,OAChBt1B,EAAcu1B,IAAmB1zC,EAAAA,EAAAA,UAAwB,IAAMwzC,EAAa/oC,IAAI,SAKvFxK,EAAAA,EAAAA,WAAU,KACR,GAAoB,MAAhBke,EAAsB,OAC1B,IAAIw1B,EAA0B,KAC1BC,GAAY,IAChB,IAAK,MAAOj1B,EAAMU,KAAOkzB,EAAYvmC,UAAW,CAC9C,GAAI2U,GAAmBtB,EAAGR,aAAc,SACxC,MAAM1a,EAAIkb,EAAGG,oBACJ,MAALrb,GAAaA,EAAIyvC,IACnBA,EAAYzvC,EACZwvC,EAAWh1B,EAEf,CACgB,MAAZg1B,GAAoBpB,EAAY9wC,KAAO,IACzCkyC,EAAW,IAAIpB,EAAYhX,QAAQ,IAErB,MAAZoY,GAAkBD,EAAgBC,IACrC,CAACpB,EAAap0B,IAEjB,MAAM01B,GAAiBhqC,EAAAA,EAAAA,SAAQ,IAAOsU,EAAe,CAACA,GAAgB,GAAK,CAACA,KACtE,SAAEyE,EAAQ,eAAE+R,EAAgBO,QAAS4e,GCtEtC,SAA0BjxB,G,IAAoByvB,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAqB,EACxE,MAAO1vB,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnD20B,EAAgBof,IAAqB/zC,EAAAA,EAAAA,UAA0B,KAC/Dk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,GAKjCg0C,EAAcnxB,EAASzb,KAAK,KA+ClC,OA7CAnH,EAAAA,EAAAA,WAAU,KACR,GAAwB,IAApB4iB,EAAS1c,OAIX,OAHA8uB,EAAY,IACZ8e,EAAkB,SAClB5e,GAAW,GAIb,IAAI5qB,GAAY,EA4BhB,OA3BA4qB,GAAW,GAEXlQ,GAAgB,CACdjW,MAAO,CACL7D,OAAQ,QACR8oC,UAAWpxB,EACX2R,MAAO,OAGR9zB,KAAM,I,IAAA,KAAEoR,GAAM,EAGb,GAAIvH,EAAW,OACf,MAAM2pC,EAAOpiC,GAAM+iB,SAAW,GAC9BI,EAAYif,EAAKjwC,OAAQob,IAAQtb,GAAesb,KAChD00B,EAAkBG,EAAKjwC,OAAQob,GAAOtb,GAAesb,OAEtDoH,MAAM,KACAlc,IACH0qB,EAAY,IACZ8e,EAAkB,OAGrBrtB,QAAQ,KACFnc,GAAW4qB,GAAW,KAGxB,KACL5qB,GAAY,IAMb,CAACypC,EAAa1B,IAEV,CAAE1vB,WAAU+R,iBAAgBO,UACrC,CDc2Dif,CAAiBN,EAAgB,GAEpFO,GAAevqC,EAAAA,EAAAA,SAAuB,IACrCq3B,EACEA,EAAQC,SACZl9B,OAAQowC,GAAoC,iBAAvBA,EAAEC,YAAY31B,MACnC/R,IAAKynC,IACJ,MAAM11B,EAAO01B,EAAEC,WAAY31B,KACrB5F,EAAW,EAAGu7B,WAAYC,eAA+C,EACzEv7B,EAAS,EAAGs7B,WAAYE,aAA6C,IACrEv7B,EAAU,EAAGq7B,WAAYG,cAA8C,GAE7E,MAAO,CACL91B,OACAhG,QAHW07B,EAAEhT,SAIbtoB,WACAC,SACAC,UACA2F,QAAS2zB,EAAY9nC,IAAIkU,IAAS,QAfnB,GAkBpB,CAACuiB,EAASqR,IAEPmC,GAAa7qC,EAAAA,EAAAA,SACjB,IAAOq3B,EVnFJ,SACLC,EACAoR,GAEA,MAAMzd,EAASqM,EACZl9B,OAAQowC,GAAoC,iBAAvBA,EAAEC,YAAY31B,MACnC/R,IAAKynC,IAAO,CACX11B,KAAM01B,EAAEC,WAAY31B,KACpBg2B,MAAO,EAAGL,WAAYK,OAAgC,KAEvD3vC,KAAK,CAACP,EAAGC,IAAMD,EAAEkwC,MAAQjwC,EAAEiwC,OAASlwC,EAAEka,KAAKha,cAAcD,EAAEia,OAC3D/R,IAAK,I,IAAA,KAAE+R,GAAM,EACZ,MAAMU,EAAKkzB,EAAY9nC,IAAIkU,GAC3B,MAAO,CAAEA,OAAM6B,GAAInB,GAAIoB,cAAgB,KAAMmqB,UAAWvrB,GAAIR,aAAe,QAE/E,MAAO,CACLksB,KAAMjW,EAAO7wB,OAAQE,GAvBK,OAuBCA,EAAEwa,MAC7BqsB,IAAKlW,EAAO1wB,KAAMD,GAxBQ,OAwBFA,EAAEwa,OAAgC,KAE9D,CUgEqBi2B,CAAe1T,EAAQC,SAAUoR,GAAe,KACjE,CAACrR,EAASqR,IAGNsC,GAAchrC,EAAAA,EAAAA,SAClB,IAAMuqC,EAAahwC,KAAM/D,GAAMA,EAAEse,OAASR,IAAiB,KAC3D,CAACi2B,EAAcj2B,IAGXqrB,GAAS3/B,EAAAA,EAAAA,SAAQ,KACrB,MAAM3H,EAAQg/B,GAASC,SAAS/8B,KAAMiwC,GAAMA,EAAEC,YAAY31B,OAASR,IAAem2B,WAClF,MAAO,CACL7pB,IAAK,GAAQqqB,WAA2C,KACxDnqB,IAAK,GAAQoqB,WAA2C,OAEzD,CAAC7T,EAAS/iB,IAEP4I,GAAYld,EAAAA,EAAAA,SAChB,IHvGG,SACL+Y,EACAC,GAEA,MAAMmyB,EAAS,IAAIlsC,IACbka,EAAS,IAAInd,IAAIgd,GAEvB,IAAK,MAAMxD,KAAMuD,EAAU,CACzB,IAAKvD,EAAGR,cAAgBmE,EAAOld,IAAIuZ,EAAG4D,gBAAiB,SACvD,GAAIlB,GAAM1C,IAAOtb,GAAesb,GAAK,SAErC,IAAI0xB,EAASiE,EAAOvqC,IAAI4U,EAAGR,aACtBkyB,IACHA,EAAS,CAAEjxB,KAAMT,EAAGR,aACpBm2B,EAAOvpC,IAAI4T,EAAGR,YAAakyB,IAG7B,MAAMpyB,EAAOU,EAAG4D,eAChB8tB,EAAO,GAAGpyB,SAAcU,EAAGuB,iBAC3BmwB,EAAO,GAAGpyB,cAAmBU,EAAGwB,0BAChCkwB,EAAO,GAAGpyB,UAAeU,EAAG41B,kBAC5BlE,EAAO,GAAGpyB,SAAcU,EAAG61B,iBAC3BnE,EAAO,GAAGpyB,gBAAqBU,EAAGG,oBAClCuxB,EAAO,GAAGpyB,iBAAsBU,EAAG81B,qBACnCpE,EAAO,GAAGpyB,gBAAqBU,EAAG+1B,oBAClCrE,EAAO,GAAGpyB,iBAAsBU,EAAGg2B,qBACnCtE,EAAO,GAAGpyB,gBAAqBU,EAAGc,oBAClC4wB,EAAO,GAAGpyB,iBAAsBU,EAAGi2B,oBACrC,CAEA,OAAO5mC,MAAM43B,KAAK0O,EAAO3nC,UAAUrI,KAAK,CAACP,EAAGC,IAC1C,EAAGob,KAAgBnb,cAAcD,EAAEob,MAEvC,CGsEUy1B,CAAc3yB,EAAUixB,GAC9B,CAACjxB,EAAUixB,IAGP2B,GAAa3rC,EAAAA,EAAAA,SACjB,IHzDG,SACL4rC,EACA9gB,EACA9R,GAEA,MAAMG,EAAS,IAAInd,IAAIgd,GACjBmyB,EAAS,IAAIlsC,IAAI2sC,EAAQ7oC,IAAKvM,GAAM,CAACA,EAAEyf,KAAgB,IAAKzf,MAClE,IAAK,MAAMse,KAAQqE,EAAQ,CACzB,MAAMykB,EAAQ9S,EACX1wB,OAAQob,GAAOA,EAAG4D,iBAAmBtE,GAAQU,EAAGR,aAAe9a,GAAesb,IAC9Era,KAAK,CAACP,EAAGC,IAAMD,EAAEoa,YAAala,cAAcD,EAAEma,cACjD,GAAqB,IAAjB4oB,EAAMthC,OAAc,SACxB,MAAM8oC,EAASxH,EAAM,GAAG5oB,YAClB62B,EAAU,IAAIV,EAAO3nC,UACxBpJ,OAAQ5D,GAAM,EAAGyf,KAAkBmvB,GAAqC,MAA3B5uC,EAAE,GAAGse,iBAClD3Z,KAAK,CAACP,EAAGC,IAAM,EAAGob,KAAgBnb,cAAcF,EAAEqb,OAAiB,GACtE,GAAI41B,EACF,IAAK,MAAO9rC,KAAQ4oC,GAAakD,EAAQ,GAAG/2B,KAAQ/U,iBAAqB8rC,EAAQ,GAAG/2B,KAAQ/U,KAE9F,IAAK,MAAMyV,KAAMooB,EAAO,CACtB,MAAMsJ,EAASiE,EAAOvqC,IAAI4U,EAAGR,cAAiB,CAAEiB,KAAMT,EAAGR,aACzD,IAAK,MAAOjV,EAAK+rC,KAASnD,GAAazB,EAAO,GAAGpyB,KAAQ/U,iBAAqByV,EAAGs2B,IAAS,KAC1FX,EAAOvpC,IAAI4T,EAAGR,YAAckyB,EAC9B,CACF,CACA,OAAOriC,MAAM43B,KAAK0O,EAAO3nC,UAAUrI,KAAK,CAACP,EAAGC,IAC1C,EAAGob,KAAgBnb,cAAcD,EAAEob,MAEvC,CG6BU81B,CAAiB7uB,EAAW4N,EAAgBkf,GAClD,CAAC9sB,EAAW4N,EAAgBkf,IAGxB/+B,GAAQjL,EAAAA,EAAAA,SAAQ,IACfsU,EAQE,CAAC,CAAE6J,QANC,eAATqa,EACI,GAAGlkB,QACM,oBAATkkB,EACE,GAAGlkB,aACH,GAAGA,SAEQ/B,MAAO62B,GAAW/zB,MADd,UAATmjB,EAAmB,aAAesQ,GAAYtQ,KAPlC,GASzB,CAAClkB,EAAckkB,IAEZsO,GAAQ9mC,EAAAA,EAAAA,SACZ,IACW,UAATw4B,GAAoBlkB,EAChB,CAAC,CAAE6yB,OAAQ,GAAG7yB,QAAoB8yB,OAAQ,GAAG9yB,QAAoB/B,MAAO62B,UACxErlC,EACN,CAACuQ,EAAckkB,IAGXwT,GAAchsC,EAAAA,EAAAA,SAAQ,KAC1B,IAAKsU,EAAc,MAAO,GAC1B,MAAM23B,EAAwB,WAAfzC,EAA0B,SAAW,UAC9C0C,EAA0B,CAAC,MAAO,OACxC,MAAO,IACFA,EAAQnpC,IAAKopC,IAAY,CAC1BhuB,QAAS,GAAG7J,KAAgB63B,KAAUF,IACtC15B,MAAOoO,GAAiBwrB,GACxB92B,MAAO,SAAS0L,GAAiBorB,WAEhCD,EAAQnpC,IAAKopC,IAAY,CAC1BhuB,QAAS,GAAG7J,KAAgB63B,KAAUF,gBACtC15B,MAAOoO,GAAiBwrB,GACxB92B,MAAO,SAAS0L,GAAiBorB,kCACjC1F,QAAQ,OAGX,CAACnyB,EAAck1B,IAEZpoB,GAAkBphB,EAAAA,EAAAA,SACtB,KAAO,CACL4gB,IAAK+e,EAAO/e,UAAO7c,EACnB+c,IAAK6e,EAAO7e,UAAO/c,IAErB,CAAC47B,IAGGtU,EAAUie,GAAcW,EACxBmC,GACJ,SAACt5B,MAAAA,CAAI3a,UAAU,wC,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,gC,SAAgC,eAIjD,OACE,UAAC2a,MAAAA,CAAI3a,UAAU,+C,WACb,SAACk0C,GAAUA,CACTj4B,MAAM,UACNw0B,YAAY,6EAGZc,GAAmBmB,GAAcA,EAAW3J,KAAK5kC,OAAS,IAC1D,SAAC2kC,GAAUA,CACTC,KAAM2J,EAAW3J,KACjBC,IAAK0J,EAAW1J,IAChB7sB,aAAcA,EACdC,SAAUs1B,KAGd,SAACyC,GAAWA,CACVl4B,MAAM,yBACNw0B,YAAY,6L,SAEXc,GAA2C,IAAxBa,EAAajuC,QAC/B,SAACwW,MAAAA,CAAI3a,UAAU,sE,SAAsE,sBAIrF,SAAC2a,MAAAA,CAAI3a,UAAU,kB,UACb,SAACkc,GAAgBA,CACfpF,KAAMs7B,EACNj2B,aAAcA,EACdC,SAAUs1B,QAMjBmB,IACC,SAACtL,GAAcA,CACb5qB,KAAMk2B,EAAYl2B,KAClBhG,QAASk8B,EAAYl8B,QACrBI,SAAU87B,EAAY97B,SACtBywB,OAAQA,EACRC,cAAeoL,EAAYj2B,QAC3B8qB,QACE,sB,WACE,UAACf,GAAIA,C,WACH,UAACC,GAAUA,CAAC5mC,UAAU,6D,WACpB,SAAC6mC,GAASA,CAAC7mC,UAAU,U,SAAU,sBAC/B,SAACyhB,GAAAA,EAAWA,CACVra,KAAK,SACLsD,MAAO21B,EACP3e,cAAgBvf,IACVA,GAAGivC,EAAQjvC,IAEjB1C,KAAK,K,SAEJ,OAAQuK,QAAQ2mC,IAAyC/lC,IACvD,I,IAAChD,EAAKsV,GAAM,E,OACX,SAACyE,GAAAA,EAAeA,CAAWjX,MAAO9C,E,SAC/BsV,GADmBtV,WAO9B,UAACk/B,GAAWA,C,UACT5T,EACC+gB,GAEA,SAACt5B,MAAAA,CAAI3a,UAAU,O,UACb,SAAC0uC,GAAcA,CACb5+B,KAAMiV,EACNsoB,QAAQ,OACRv6B,MAAOA,EACP67B,MAAOA,EACP1lB,gBAAiBA,OAIvB,SAACpR,IAAAA,CAAE7X,UAAU,qC,SAAqC,2FAMtD,UAAC2mC,GAAIA,C,WACH,UAACC,GAAUA,CAAC5mC,UAAU,6D,WACpB,SAAC6mC,GAASA,CAAC7mC,UAAU,U,SAAU,wBAC/B,SAACyhB,GAAAA,EAAWA,CACVra,KAAK,SACLsD,MAAO2mC,EACP3vB,cAAgBvf,IACVA,GAAGmvC,EAAcnvC,IAEvB1C,KAAK,K,SAEJ,OAAQuK,QAAQ+mC,IAA+CnmC,IAC7D,I,IAAChD,EAAKsV,GAAM,E,OACX,SAACyE,GAAAA,EAAeA,CAAWjX,MAAO9C,E,SAC/BsV,GADmBtV,WAO9B,UAACk/B,GAAWA,C,UACT5T,EACC+gB,GAEA,SAACt5B,MAAAA,CAAI3a,UAAU,O,UACb,SAACutC,GAAgBA,CACfz9B,KAAM0jC,EACNnG,QAAQ,OACRv6B,MAAO+gC,EACPrG,OAAuB,WAAf6D,EAA0B,kBAAoB,kBACtD5D,KAAqB,WAAf4D,EAA0B,KAAO,SAI5C1e,EAAexuB,OAAS,IACvB,SAAC0T,IAAAA,CAAE7X,UAAU,qC,SAAqC,oJAS5D2nC,WACE,UAAChB,GAAIA,C,WACH,SAACC,GAAUA,C,UACT,SAACC,GAASA,CAAC7mC,UAAU,U,SAAU,gCAEjC,SAAC8mC,GAAWA,CAAC9mC,UAAU,kB,UACrB,SAAC2gB,GAAaA,CAACC,SAAUA,EAAUC,SAAUgxB,YAOvD,SAACsC,GAAWA,CACVl4B,MAAM,mBACNw0B,YAAY,mG,UAEZ,SAACzI,EAAAA,GAAIA,CAACC,GAAG,qBAAqBjoC,UAAU,mD,SAAmD,iCAMnG,C,2LEhVA,SAASo0C,GAAM,G,IAAA,UAAEp0C,EAAS,KAAEoH,KAASlH,GAAtB,EACb,OACE,SAACm0C,QAAAA,CACCjtC,KAAMA,EACN/G,YAAU,QACVL,WAAWQ,EAAAA,EAAAA,IACT,8bACA,gFACA,yGACAR,MAEEE,GAGV,C,aCbA,SAASo0C,GAAU,G,IAAA,UACjBt0C,EAAS,YACTu0C,EAAc,aAAY,WAC1BC,GAAa,KACVt0C,GAJc,EAMjB,OACE,SAACu0C,GAAAA,EAAuB,CACtBp0C,YAAU,YACVm0C,WAAYA,EACZD,YAAaA,EACbv0C,WAAWQ,EAAAA,EAAAA,IACT,iKACAR,MAEEE,GAGV,C,0BCfA,SAASw0C,GAAM,G,OAAKx0C,GAAL,EACb,OAAO,SAACy0C,GAAAA,GAAmB,CAACt0C,YAAU,WAAYH,GACpD,CAUA,SAAS00C,GAAY,G,OAAK10C,GAAL,EACnB,OAAO,SAACy0C,GAAAA,GAAqB,CAACt0C,YAAU,kBAAmBH,GAC7D,CAEA,SAAS20C,GAAa,G,IAAA,UACpB70C,KACGE,GAFiB,EAIpB,OACE,SAACy0C,GAAAA,GAAsB,CACrBt0C,YAAU,gBACVL,WAAWQ,EAAAA,EAAAA,IACT,yJACAR,MAEEE,GAGV,CAEA,SAAS40C,GAAa,G,IAAA,UACpB90C,EAAS,SACTsV,EAAQ,KACRy/B,EAAO,QAAO,gBACdC,GAAkB,KACf90C,GALiB,EAUpB,OACE,UAAC00C,GAAAA,C,WACC,SAACC,GAAAA,CAAAA,IACD,UAACF,GAAAA,GAAsB,CACrBt0C,YAAU,gBACVL,WAAWQ,EAAAA,EAAAA,IACT,6MACS,UAATu0C,GACE,mIACO,SAATA,GACE,gIACO,QAATA,GACE,2GACO,WAATA,GACE,oHACF/0C,MAEEE,E,UAEHoV,EACA0/B,IACC,UAACL,GAAAA,GAAoB,CAAC30C,UAAU,6O,WAC9B,SAACi1C,GAAAA,EAAKA,CAACj1C,UAAU,YACjB,SAAC+b,OAAAA,CAAK/b,UAAU,U,SAAU,kBAMtC,CAEA,SAASk1C,GAAY,G,IAAA,UAAEl1C,KAAcE,GAAhB,EACnB,OACE,SAACya,MAAAA,CACCta,YAAU,eACVL,WAAWQ,EAAAA,EAAAA,IAAG,4BAA6BR,MACvCE,GAGV,CAYA,SAASi1C,GAAW,G,IAAA,UAAEn1C,KAAcE,GAAhB,EAClB,OACE,SAACy0C,GAAAA,GAAoB,CACnBt0C,YAAU,cACVL,WAAWQ,EAAAA,EAAAA,IAAG,gCAAiCR,MAC3CE,GAGV,CAEA,SAASk1C,GAAiB,G,IAAA,UACxBp1C,KACGE,GAFqB,EAIxB,OACE,SAACy0C,GAAAA,GAA0B,CACzBt0C,YAAU,oBACVL,WAAWQ,EAAAA,EAAAA,IAAG,gCAAiCR,MAC3CE,GAGV,CCxHA,SAASm1C,GAAS,G,IAAA,UAAEr1C,KAAcE,GAAhB,EAChB,OACE,SAACya,MAAAA,CACCta,YAAU,WACVL,WAAWQ,EAAAA,EAAAA,IAAG,qCAAsCR,MAChDE,GAGV,C,eCLA,SAASo1C,GAAgB,G,IAAA,cACvBC,EAAgB,KACbr1C,GAFoB,EAIvB,OACE,SAACs1C,GAAAA,GAAyB,CACxBn1C,YAAU,mBACVk1C,cAAeA,KACXr1C,GAGV,CAEA,SAASmmB,GAAQ,G,OAAKnmB,GAAL,EACf,OAAO,SAACs1C,GAAAA,GAAqB,CAACn1C,YAAU,aAAcH,GACxD,CAEA,SAASu1C,GAAe,G,OAAKv1C,GAAL,EACtB,OAAO,SAACs1C,GAAAA,GAAwB,CAACn1C,YAAU,qBAAsBH,GACnE,CAEA,SAASw1C,GAAe,G,IAAA,UACtB11C,EAAS,WACT21C,EAAa,EAAC,SACdrgC,KACGpV,GAJmB,EAMtB,OACE,SAACs1C,GAAAA,GAAuB,C,UACtB,UAACA,GAAAA,GAAwB,CACvBn1C,YAAU,kBACVs1C,WAAYA,EACZ31C,WAAWQ,EAAAA,EAAAA,IACT,oaACAR,MAEEE,E,UAEHoV,GACD,SAACkgC,GAAAA,GAAsB,CAACx1C,UAAU,2GAI1C,CCrBA,MAiBM41C,GAAiB5L,EAAAA,cAAgD,MAEvE,SAAS6L,KACP,MAAMlgB,EAAUqU,EAAAA,WAAiB4L,IACjC,IAAKjgB,EACH,MAAM,IAAIzsB,MAAM,qDAGlB,OAAOysB,CACT,CAEA,SAASmgB,GAAgB,G,IAAA,YACvBC,GAAc,EACdC,KAAMC,EACNC,aAAcC,EAAW,UACzBn2C,EAAS,MACTyK,EAAK,SACL6K,KACGpV,GAPoB,EAavB,MAAMk2C,EChED,WACL,MAAOA,EAAUC,GAAerM,EAAAA,cAAoCp+B,GAYpE,OAVAo+B,EAAAA,UAAgB,KACd,MAAMsM,EAAM1rB,OAAO2rB,WAAW,sBACxBC,EAAW,KACfH,EAAYzrB,OAAO/R,WARC,MAYtB,OAFAy9B,EAAI9jC,iBAAiB,SAAUgkC,GAC/BH,EAAYzrB,OAAO/R,WAXG,KAYf,IAAMy9B,EAAI7iC,oBAAoB,SAAU+iC,IAC9C,MAEMJ,CACX,CDkDmBK,IACVC,EAAYC,GAAiB3M,EAAAA,UAAe,IAI5C4M,EAAOC,GAAY7M,EAAAA,SAAe+L,GACnCC,EAAOC,GAAYW,EACnBE,EAAU9M,EAAAA,YACbt/B,IACC,MAAMqsC,EAA6B,mBAAVrsC,EAAuBA,EAAMsrC,GAAQtrC,EAC1DyrC,EACFA,EAAYY,GAEZF,EAASE,GAIX/uC,SAASkN,OAAS,iBAA0B6hC,6BAE9C,CAACZ,EAAaH,IAIVgB,EAAgBhN,EAAAA,YAAkB,IAC/BoM,EAAWO,EAAeX,IAAUA,GAAQc,EAASd,IAAUA,GACrE,CAACI,EAAUU,EAASH,IAGvB3M,EAAAA,UAAgB,KACd,MAAMiN,EAAiB1jC,IAjEO,MAkExBA,EAAM3L,MAAsC2L,EAAM2jC,SAAW3jC,EAAM4jC,WACrE5jC,EAAM+7B,iBACN0H,MAKJ,OADApsB,OAAOpY,iBAAiB,UAAWykC,GAC5B,IAAMrsB,OAAOnX,oBAAoB,UAAWwjC,IAClD,CAACD,IAIJ,MAAMI,EAAQpB,EAAO,WAAa,YAE5BqB,EAAerN,EAAAA,QACnB,KAAO,CACLoN,QACApB,OACAc,UACAV,WACAM,aACAC,gBACAK,kBAEF,CAACI,EAAOpB,EAAMc,EAASV,EAAUM,EAAYC,EAAeK,IAG9D,OACE,SAACpB,GAAev/B,SAAQ,CAAC3L,MAAO2sC,E,UAC9B,SAAC/B,GAAeA,CAACC,cAAe,E,UAC9B,SAAC56B,MAAAA,CACCta,YAAU,kBACVoK,MACE,CACE,kBAvGQ,QAwGR,uBAtGa,UAuGVA,GAGPzK,WAAWQ,EAAAA,EAAAA,IACT,kFACAR,MAEEE,E,SAEHoV,OAKX,CAEA,SAASgiC,GAAQ,G,IAAA,KACfvC,EAAO,OAAM,QACb71C,EAAU,UAAS,YACnBq4C,EAAc,YAAW,UACzBv3C,EAAS,SACTsV,KACGpV,GANY,EAYf,MAAM,SAAEk2C,EAAQ,MAAEgB,EAAK,WAAEV,EAAU,cAAEC,GAAkBd,KAEvD,MAAoB,SAAhB0B,GAEA,SAAC58B,MAAAA,CACCta,YAAU,UACVL,WAAWQ,EAAAA,EAAAA,IACT,8EACAR,MAEEE,E,SAEHoV,IAKH8gC,GAEA,SAAC1B,GAAKA,CAACsB,KAAMU,EAAYR,aAAcS,KAAmBz2C,E,UACxD,UAAC40C,GAAYA,CACX0C,eAAa,UACbn3C,YAAU,UACVo3C,cAAY,OACZz3C,UAAU,+EACVyK,MACE,CACE,kBA/Je,SAkKnBsqC,KAAMA,E,WAEN,UAACG,GAAWA,CAACl1C,UAAU,U,WACrB,SAACm1C,GAAUA,C,SAAC,aACZ,SAACC,GAAgBA,C,SAAC,qCAEpB,SAACz6B,MAAAA,CAAI3a,UAAU,8B,SAA+BsV,UAOpD,UAACqF,MAAAA,CACC3a,UAAU,qDACV03C,aAAYN,EACZO,mBAA4B,cAAVP,EAAwBG,EAAc,GACxDj3C,eAAcpB,EACd04C,YAAW7C,EACX10C,YAAU,U,WAGV,SAACsa,MAAAA,CACCta,YAAU,cACVL,WAAWQ,EAAAA,EAAAA,IACT,0FACA,yCACA,qCACY,aAAZtB,GAAsC,UAAZA,EACtB,mFACA,6DAGR,SAACyb,MAAAA,CACCta,YAAU,oBACVL,WAAWQ,EAAAA,EAAAA,IACT,uHACS,SAATu0C,EACI,iFACA,mFAEQ,aAAZ71C,GAAsC,UAAZA,EACtB,2FACA,0HACJc,MAEEE,E,UAEJ,SAACya,MAAAA,CACC68B,eAAa,UACbn3C,YAAU,gBACVL,UAAU,mN,SAETsV,QAKX,CAEA,SAASuiC,GAAe,G,IAAA,UAAE73C,EAAS,QAAE4d,KAAY1d,GAAzB,EACtB,MAAM,cAAE82C,GAAkBnB,KAE1B,OACE,UAAC91C,EAAAA,EAAMA,CACLy3C,eAAa,UACbn3C,YAAU,kBACVnB,QAAQ,QACRO,KAAK,OACLO,WAAWQ,EAAAA,EAAAA,IAAG,SAAUR,GACxB4d,QAAUrK,IACRqK,IAAUrK,GACVyjC,QAEE92C,E,WAEJ,SAAC43C,GAAAA,EAAaA,CAAAA,IACd,SAAC/7B,OAAAA,CAAK/b,UAAU,U,SAAU,qBAGhC,CA2BA,SAAS+3C,GAAa,G,IAAA,UAAE/3C,KAAcE,GAAhB,EACpB,OACE,SAAC83C,OAAAA,CACC33C,YAAU,gBACVL,WAAWQ,EAAAA,EAAAA,IACT,qDACA,kNACAR,MAEEE,GAGV,CAaA,SAAS+3C,GAAc,G,IAAA,UAAEj4C,KAAcE,GAAhB,EACrB,OACE,SAACya,MAAAA,CACCta,YAAU,iBACVm3C,eAAa,SACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,0BAA2BR,MACrCE,GAGV,CAEA,SAASg4C,GAAc,G,IAAA,UAAEl4C,KAAcE,GAAhB,EACrB,OACE,SAACya,MAAAA,CACCta,YAAU,iBACVm3C,eAAa,SACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,0BAA2BR,MACrCE,GAGV,CAaA,SAASi4C,GAAe,G,IAAA,UAAEn4C,KAAcE,GAAhB,EACtB,OACE,SAACya,MAAAA,CACCta,YAAU,kBACVm3C,eAAa,UACbx3C,WAAWQ,EAAAA,EAAAA,IACT,iGACAR,MAEEE,GAGV,CAEA,SAASk4C,GAAa,G,IAAA,UAAEp4C,KAAcE,GAAhB,EACpB,OACE,SAACya,MAAAA,CACCta,YAAU,gBACVm3C,eAAa,QACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,4CAA6CR,MACvDE,GAGV,CAEA,SAASm4C,GAAkB,G,IAAA,UACzBr4C,EAAS,QACTC,GAAU,KACPC,GAHsB,EAKzB,MAAMC,EAAOF,EAAUG,EAAAA,GAAY,MAEnC,OACE,SAACD,EAAAA,CACCE,YAAU,sBACVm3C,eAAa,cACbx3C,WAAWQ,EAAAA,EAAAA,IACT,2OACA,8EACAR,MAEEE,GAGV,CAyBA,SAASo4C,GAAoB,G,IAAA,UAAEt4C,KAAcE,GAAhB,EAC3B,OACE,SAACya,MAAAA,CACCta,YAAU,wBACVm3C,eAAa,gBACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,iBAAkBR,MAC5BE,GAGV,CAEA,SAASq4C,GAAY,G,IAAA,UAAEv4C,KAAcE,GAAhB,EACnB,OACE,SAACumC,KAAAA,CACCpmC,YAAU,eACVm3C,eAAa,OACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,qCAAsCR,MAChDE,GAGV,CAEA,SAASs4C,GAAgB,G,IAAA,UAAEx4C,KAAcE,GAAhB,EACvB,OACE,SAACwmC,KAAAA,CACCrmC,YAAU,oBACVm3C,eAAa,YACbx3C,WAAWQ,EAAAA,EAAAA,IAAG,2BAA4BR,MACtCE,GAGV,CAEA,MAAMu4C,IAA4Bz5C,EAAAA,EAAAA,GAChC,ozBACA,CACEC,SAAU,CACRC,QAAS,CACPC,QAAS,+DACTE,QACE,gLAEJI,KAAM,CACJN,QAAS,cACTQ,GAAI,cACJC,GAAI,oDAGRE,gBAAiB,CACfZ,QAAS,UACTO,KAAM,aAKZ,SAASi5C,GAAkB,G,IAAA,QACzBz4C,GAAU,EAAK,SACf04C,GAAW,EAAK,QAChBz5C,EAAU,UAAS,KACnBO,EAAO,UAAS,QAChBm5C,EAAO,UACP54C,KACGE,GAPsB,EAazB,MAAMC,EAAOF,EAAUG,EAAAA,GAAY,UAC7B,SAAEg2C,EAAQ,MAAEgB,GAAUvB,KAEtBnM,GACJ,SAACvpC,EAAAA,CACCE,YAAU,sBACVm3C,eAAa,cACbj3C,YAAWd,EACXo5C,cAAaF,EACb34C,WAAWQ,EAAAA,EAAAA,IAAGi4C,GAA0B,CAAEv5C,UAASO,SAASO,MACxDE,IAIR,OAAK04C,GAIkB,iBAAZA,IACTA,EAAU,CACRtjC,SAAUsjC,KAKZ,UAACvyB,GAAOA,C,WACN,SAACovB,GAAcA,CAACx1C,SAAO,E,SAAEypC,KACzB,SAACgM,GAAcA,CACbX,KAAK,QACL33B,MAAM,SACN07B,OAAkB,cAAV1B,GAAyBhB,KAC7BwC,QAhBDlP,CAoBX,CEreA,MAAMqP,GAAiC,CACrC,CAAE98B,MAAO,QAASgsB,GAAI,aAAcpoC,KAAMm5C,GAAAA,EAAU1oB,KAAK,GACzD,CAAErU,MAAO,mBAAoBgsB,GAAI,qBAAsBpoC,KAAMo5C,GAAAA,GAC7D,CAAEh9B,MAAO,UAAWgsB,GAAI,eAAgBpoC,KAAMq5C,GAAAA,EAAeC,uBAAuB,GACpF,CACEl9B,MAAO,cACPgsB,GAAI,mBACJpoC,KAAMu5C,GAAAA,EACND,uBAAuB,IAIrBE,GAA8B,CAClC,CAAEp9B,MAAO,UAAWgsB,GAAI,oBAAqBpoC,KAAMy5C,GAAAA,GACnD,CAAEr9B,MAAO,WAAYgsB,GAAI,qBAAsBpoC,KAAM05C,GAAAA,GACrD,CAAEt9B,MAAO,aAAcgsB,GAAI,iBAAkBpoC,KAAM25C,GAAAA,GACnD,CAAEv9B,MAAO,aAAcgsB,GAAI,kBAAmBpoC,KAAM45C,GAAAA,IAOtD,SAASC,GAAe,G,IAAA,MAAEC,EAAK,OAAEC,GAAS,GAAlB,EACtB,MAAM/uB,GAAWgvB,EAAAA,EAAAA,MAEjB,OACE,SAACtB,GAAWA,CAACv4C,UAAW45C,EAAS,QAAU,U,SACxCD,EAAM/uC,IAAK6gC,IACV,MAAMkN,EAAWlN,EAAKnb,IAClBzF,EAASgE,WAAa4c,EAAKxD,GAC3Bpd,EAASgE,WAAa4c,EAAKxD,IAAMpd,EAASgE,SAASlhB,WAAW,GAAG89B,EAAKxD,OAE1E,OACE,SAACuQ,GAAeA,C,UACd,SAACE,GAAiBA,CAChBz4C,SAAO,EACP04C,SAAUA,EACVC,QAASnN,EAAKxvB,MACdjc,WAAWQ,EAAAA,EAAAA,IACT,wBACAo5C,EACI,uDACA,iDAtBlB,4K,UA0BY,UAACE,EAAAA,GAAOA,CAAC7R,GAAIwD,EAAKxD,GAAI3X,IAAKmb,EAAKnb,I,WAC9B,SAACmb,EAAK5rC,KAAI,KACV,SAACkc,OAAAA,C,SAAM0vB,EAAKxvB,cAfIwvB,EAAKxD,OAuBrC,CA6BO,SAAS8R,GAAW,G,OAAK75C,GAAL,EACzB,MAAM,KAAE2U,EAAI,OAAEuB,GAAWE,IACnB0jC,EAAYjB,GAAW92C,OAC1BwpC,IAAUA,EAAK0N,uBAAyBtkC,GAAMolC,qBAE3C,KAAE1zB,EAAI,UAAE2zB,GA3BT,SAAwBrlC,GAI7B,IAAKA,EAAM,MAAO,CAAE0R,KAAM,sBAAuB2zB,UAAW,KAC5D,MAAMC,EAAYtlC,EAAKulC,SAAWvlC,EAAKulC,UAAYvlC,EAAKiB,MAAQjB,EAAKulC,QAAQp1C,OAAS,KAChFF,GAASq1C,GAAatlC,EAAKiB,MAAMrR,MAAM,KAAK,IAAIA,MAAM,YAAYxC,OAAO8nB,SAC/E,OAAqB,IAAjBjlB,EAAMX,OAAqB,CAAEoiB,KAAM1R,EAAKiB,MAAOokC,UAAW,KAYvD,CAAE3zB,KATP4zB,GACAr1C,EACG8F,IAAKi6B,GACY,IAAhBA,EAAK1gC,OAAe,GAAG0gC,EAAKwV,iBAAmBxV,EAAK,GAAGwV,cAAgBxV,EAAKxiC,MAAM,IAEnF+C,KAAK,KAIK80C,WAFI,IAAjBp1C,EAAMX,OAAeW,EAAM,GAAG,GAAKA,EAAM,GAAG,GAAKA,EAAMA,EAAMX,OAAS,GAAG,IACzEk2C,cAEJ,CAO8BC,CAAezlC,GAE3C,OACE,UAACyiC,GAAOA,CAACC,YAAY,UAAWr3C,E,WAC9B,UAAC+3C,GAAaA,CAACj4C,UAAU,+J,WACvB,UAACgoC,EAAAA,GAAIA,CACHC,GAAG,aACHjoC,UAAU,kF,WAEV,SAACu6C,MAAAA,CACCC,IAAKC,GACLC,IAAI,qCACJ16C,UAAU,0DAGZ,SAACu6C,MAAAA,CACCC,IAAKG,GACLD,IAAI,SACJ16C,UAAU,8DAGd,SAAC63C,GAAcA,CACb73C,UAAU,mEACVuX,aAAW,kCAIf,UAACoD,MAAAA,CAAI3a,UAAU,0G,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAgC,aAChD,SAAC+b,OAAAA,CAAK/b,UAAU,4B,SAA4B,iBAG9C,UAACm4C,GAAcA,CAACn4C,UAAU,Q,WACxB,SAACo4C,GAAYA,CAACp4C,UAAU,+C,UACtB,SAACs4C,GAAmBA,C,UAClB,SAACsC,MAAAA,CAAIrjC,aAAW,Y,UACd,SAACmiC,GAAAA,CAAeC,MAAOK,WAQ7B,UAAC5B,GAAYA,CAACp4C,UAAU,kF,WACtB,SAACq4C,GAAiBA,CAACr4C,UAAU,sG,SAAsG,YAGnI,SAACs4C,GAAmBA,C,UAClB,SAACsC,MAAAA,CAAIrjC,aAAW,S,UACd,SAACmiC,GAAAA,CAAeC,MAAON,GAAcO,QAAM,eAMnD,SAAC1B,GAAaA,CAACl4C,UAAU,wC,UACvB,UAAC2a,MAAAA,CAAI3a,UAAU,yH,WACb,SAAC+b,OAAAA,CACCytB,cAAY,OACZxpC,UAAU,2J,SAETk6C,KAEH,UAACn+B,OAAAA,CAAK/b,UAAU,2E,WACd,SAAC+b,OAAAA,CAAK/b,UAAU,qC,SAAsCumB,KACtD,SAACxK,OAAAA,CAAK/b,UAAU,yC,SACb6U,GAAMiB,OAAS,4BAGpB,SAAC/V,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,kKACV4d,QAASxH,EACTmB,aAAW,YACX0E,MAAM,Y,UAEN,SAAC4+B,GAAAA,EAAMA,CAAC76C,UAAU,yBAM9B,C,+ECjNA,MAAM86C,GAAW,I,OAAK56C,GAAqB,EACzC,MAAM,MAAEsqC,EAAQ,WAAauQ,EAAAA,GAAAA,KAE7B,OACE,SAACC,GAAAA,GAAMA,CACLxQ,MAAOA,EACPxqC,UAAU,gBACVi7C,MAAO,CACLC,SAAS,SAACC,GAAAA,EAAeA,CAACn7C,UAAU,WACpCo7C,MAAM,SAACC,GAAAA,EAAQA,CAACr7C,UAAU,WAC1Bs7C,SAAS,SAACC,GAAAA,EAAiBA,CAACv7C,UAAU,WACtC8J,OAAO,SAAC0xC,GAAAA,EAAYA,CAACx7C,UAAU,WAC/BkzB,SAAS,SAACuoB,GAAAA,EAAWA,CAACz7C,UAAU,yBAElCyK,MACE,CACE,cAAe,iBACf,gBAAiB,4BACjB,kBAAmB,gBACnB,kBAAmB,oBAGnBvK,KCxBJw7C,IAAkBC,EAAAA,EAAAA,MAAK,IAAM,6BAQ5B,MAAMC,WAA6BC,EAAAA,UAIxCzE,MAAQ,CAAElzB,MAAM,GAEhB,+BAAO43B,GACL,MAAO,CAAE53B,MAAM,EACjB,CAEA63B,iBAAAA,CAAkB73B,EAAak3B,GAC7BvxC,QAAQC,MAAM,sCAAuCoa,EAAMk3B,EAAKY,eAClE,CAEA3e,MAAAA,GACE,OAAKlE,KAAKie,MAAMlzB,MAEd,UAACvJ,MAAAA,CAAIrD,KAAK,QAAQtX,UAAU,uD,WAC1B,SAAC6X,IAAAA,C,SAAE,oHAIH,SAAC9X,EAAAA,EAAMA,CACLqH,KAAK,SACLwW,QAAS,KAAOub,KAAKj5B,MAAM+7C,SAAY,KAAMrxB,OAAOC,SAASqxB,a,SAC9D,4BAVwB/iB,KAAKj5B,MAAMoV,QAe1C,E,mDCvCF,SAAS6mC,GAAM,G,IAAA,UAAEn8C,KAAcE,GAAhB,EACb,OACE,SAACk8C,GAAAA,EAAmB,CAClB/7C,YAAU,QACVL,WAAWQ,EAAAA,EAAAA,IACT,sNACAR,MAEEE,GAGV,C,qCCZA,SAASm8C,GAAO,G,OAAKn8C,GAAL,EACd,OAAO,SAACo8C,GAAAA,GAAoB,CAACj8C,YAAU,YAAaH,GACtD,CAMA,SAASq8C,GAAY,G,OAAKr8C,GAAL,EACnB,OAAO,SAACo8C,GAAAA,GAAqB,CAACj8C,YAAU,kBAAmBH,GAC7D,CAEA,SAASs8C,GAAc,G,IAAA,UACrBx8C,EAAS,KACTP,EAAO,UAAS,SAChB6V,KACGpV,GAJkB,EAQrB,OACE,UAACo8C,GAAAA,GAAuB,CACtBj8C,YAAU,iBACVE,YAAWd,EACXO,WAAWQ,EAAAA,EAAAA,IACT,gzBACAR,MAEEE,E,UAEHoV,GACD,SAACgnC,GAAAA,GAAoB,CAACr8C,SAAO,E,UAC3B,SAACw8C,GAAAA,EAAeA,CAACz8C,UAAU,0BAInC,CAEA,SAAS08C,GAAc,G,IAAA,UACrB18C,EAAS,SACTsV,EAAQ,SACR8Q,EAAW,eAAc,MACzBhJ,EAAQ,YACLld,GALkB,EAOrB,OACE,SAACo8C,GAAAA,GAAsB,C,UACrB,UAACA,GAAAA,GAAuB,CACtBj8C,YAAU,iBACVL,WAAWQ,EAAAA,EAAAA,IACT,gjBACa,WAAb4lB,GACE,kIACFpmB,GAEFomB,SAAUA,EACVhJ,MAAOA,KACHld,E,WAEJ,SAACy8C,GAAAA,CAAAA,IACD,SAACL,GAAAA,GAAwB,CACvBt8C,WAAWQ,EAAAA,EAAAA,IACT,MACa,WAAb4lB,GACE,uG,SAGH9Q,KAEH,SAACsnC,GAAAA,CAAAA,OAIT,CAYA,SAASC,GAAW,G,IAAA,UAClB78C,EAAS,SACTsV,KACGpV,GAHe,EAKlB,OACE,UAACo8C,GAAAA,GAAoB,CACnBj8C,YAAU,cACVL,WAAWQ,EAAAA,EAAAA,IACT,4aACAR,MAEEE,E,WAEJ,SAAC6b,OAAAA,CACC1b,YAAU,wBACVL,UAAU,6D,UAEV,SAACs8C,GAAAA,GAA6B,C,UAC5B,SAACQ,GAAAA,EAASA,CAAC98C,UAAU,gBAGzB,SAACs8C,GAAAA,GAAwB,C,SAAEhnC,MAGjC,CAeA,SAASqnC,GAAqB,G,IAAA,UAC5B38C,KACGE,GAFyB,EAI5B,OACE,SAACo8C,GAAAA,GAA8B,CAC7Bj8C,YAAU,0BACVL,WAAWQ,EAAAA,EAAAA,IAAG,uDAAwDR,MAClEE,E,UAEJ,SAAC68C,GAAAA,EAAaA,CAAC/8C,UAAU,YAG/B,CAEA,SAAS48C,GAAuB,G,IAAA,UAC9B58C,KACGE,GAF2B,EAI9B,OACE,SAACo8C,GAAAA,GAAgC,CAC/Bj8C,YAAU,4BACVL,WAAWQ,EAAAA,EAAAA,IAAG,uDAAwDR,MAClEE,E,UAEJ,SAACu8C,GAAAA,EAAeA,CAACz8C,UAAU,YAGjC,CClIA,MAAMg9C,GAGF,CACFC,SAAU,CAAE//B,MAAO,WAAYhe,QAAS,aACxCg+C,WAAY,CAAEhgC,MAAO,aAAche,QAAS,WAC5Ci+C,UAAW,CAAEjgC,MAAO,cAAehe,QAAS,WAC5Ck+C,WAAY,CAAElgC,MAAO,aAAche,QAAS,WAC5C45B,MAAO,CAAE5b,MAAO,SAAUhe,QAAS,WACnCm+C,UAAW,CAAEngC,MAAO,eAAgBhe,QAAS,WAC7Co+C,SAAU,CAAEpgC,MAAO,eAAgBhe,QAAS,aAC5Cq+C,QAAS,CAAErgC,MAAO,aAAche,QAAS,gBAM3C,SAASs+C,GAAa,G,IAAA,MACpBtgC,EAAK,QACLb,EAAO,UACPohC,EAAS,UACTC,EAAS,OACTC,GALoB,EAapB,MAAM5+B,EAAO0+B,IAAcphC,EAAyB,QAAdqhC,EAAsB1+B,EAAAA,EAAUC,EAAAA,EAAaC,EAAAA,EAEnF,OACE,SAAC/D,EAASA,C,UACR,UAACuuB,SAAAA,CACCtiC,KAAK,SACLpH,UAAU,uDACV4d,QAAS,IAAM+/B,EAAOthC,G,UAErBa,GACD,SAAC6B,EAAAA,CAAK/e,UAAU,iBAIxB,CAEA,MCzDM49C,GAGF,CACF7kB,QAAS,CAAE7b,MAAO,WAAYhe,QAAS,aACvC2+C,QAAS,CAAE3gC,MAAO,SAAUhe,QAAS,WACrCg8C,QAAS,CAAEh+B,MAAO,WAAYhe,QAAS,WACvC4+C,QAAS,CAAE5gC,MAAO,UAAWhe,QAAS,gBAGlC6+C,GAA0C,CAC9CC,QAAS,gBACTC,QAAS,iBCiDLC,GAAe9G,IACnB,MAUM,MAAEl6B,EAAK,QAAEhe,GAPX,CACFi/C,KAAM,CAAEjhC,MAAO,WAAYhe,QAAS,aACpCk/C,UAAW,CAAElhC,MAAO,YAAahe,QAAS,WAC1Cm/C,WAAY,CAAEnhC,MAAO,aAAche,QAAS,WAC5CuT,KAAM,CAAEyK,MAAO,QAAShe,QAAS,WACjC4K,MAAO,CAAEoT,MAAO,OAAQhe,QAAS,gBAECk4C,GACpC,OAAO,SAAC38B,EAAKA,CAACvb,QAASA,E,SAAUge,KAG7BohC,GAAe,I,IAAA,QAAEC,EAAO,QAAEC,GAA0D,EACxF,OAAuB,IAAnBD,EAAQp6C,OAAqB,MAG/B,UAACgwC,GAAWA,CAACl4B,MAAM,W,WACjB,SAACtB,MAAAA,CAAI3a,UAAU,a,SACZu+C,EAAQ3zC,IAAI,CAACyjB,EAAO5qB,KACnB,UAACkX,MAAAA,CAAY3a,UAAU,8D,WACrB,SAAC+b,OAAAA,CAAK/b,UAAU,0B,SAA2BquB,EAAMowB,KAAKj0C,QACtD,UAACuR,OAAAA,CAAK/b,UAAU,6C,UAA8CquB,EAAMqwB,SAAS,OAC5ER,GAAY7vB,EAAM+oB,OAClB/oB,EAAMvkB,QAAS,SAACiS,OAAAA,CAAK/b,UAAU,2B,SAA4BquB,EAAMvkB,UAJ1DrG,OAQd,SAACkX,MAAAA,CAAI3a,UAAU,O,UACb,SAACD,EAAAA,EAAMA,CAACb,QAAQ,UAAUO,KAAK,KAAKme,QAAS4gC,E,SAAS,wBAQxDG,GAAY,I,IAAA,SAChBC,EAAQ,WACRC,EAAU,YACVC,EAAW,OACXC,EAAM,QACNnhC,EAAO,MACPV,GAQD,E,OACC,SAACvC,MAAAA,CACCkkC,WAAYA,EACZC,YAAaA,EACbC,OAAQA,EACRnhC,QAASA,EACT5d,UAAW,mJACT4+C,EAAW,4BAA8B,yC,SAG1C1hC,KC/HL,GAXwB,KACtB,MAAM2N,GAAWgvB,EAAAA,EAAAA,MAEjB,OACE,SAAC3F,GAAUA,CACTj4B,MAAM,SACNw0B,YAAa,8CAA8C5lB,EAASgE,c,gDCgI1E,MAAMmwB,GAA2B,CAC/BC,YAAa,CAAC,YACdx3C,OAAQ,CACNy3C,iBAAiB,EACjBC,eAAe,EACfC,iBAAiB,EACjBC,cAAc,EACdC,UAAU,EACVC,aAAa,EACbC,gBAAgB,EAChBC,QAAQ,EACRC,eAAe,EACfC,mBAAmB,EACnBC,UAAU,EACVC,QAAQ,GAEVC,WAAY,CACVH,kBAAmB,MAErBI,cAAe,CACbJ,kBAAmB,KACnBR,cAAe,KACfC,gBAAiB,MAEnBj4C,QAAS,QACT64C,eAAgB,MAChBljB,SAAU,UACVmjB,YAAa,KACbC,SAAU,KACVC,aAAc,KACdC,eAAgB,GAChBxuB,gBAAiB,EACjByuB,oBAAoB,EACpBC,MAAM,EACNC,gBAAiB,GACjBC,aAAc,CAAEC,SAAS,EAAOxtC,KAAM,MACtCytC,OAAQ,CAAED,SAAS,EAAOpoC,KAAM,KAAM4L,OAAO,EAAO+I,UAAW,KAAM9I,KAAM,MAC3Ey8B,SAAU,CACRC,cAAe,GACfC,cAAe,GACfC,mBAAmB,EACnBC,kBAAkB,EAClB78B,KAAM,OAgDV,SAAS88B,GAAQ5J,EAAmB6J,GAClC,OAAQA,EAAO75C,MACb,IAAK,eACH,MAAO,IACFgwC,EACH3vC,OAAQ,IACH2vC,EAAM3vC,OACT,CAACw5C,EAAOC,QAAS9J,EAAM3vC,OAAOw5C,EAAOC,SAG3C,IAAK,eACH,MAAO,IACF9J,EACH0I,WAAY,IAAK1I,EAAM0I,WAAY,CAACmB,EAAOvsB,MAAOusB,EAAO/8B,OAE7D,IAAK,kBACH,MAAO,IACFkzB,EACH2I,cAAe,IAAK3I,EAAM2I,cAAe,CAACkB,EAAOvsB,MAAOusB,EAAOE,UAEnE,IAAK,mBACH,MAAO,IACF/J,EACH6H,YAAa7H,EAAM6H,YAAYhyC,SAASg0C,EAAOjhC,WAC3Co3B,EAAM6H,YAAYh9C,OAAQoH,GAAMA,IAAM43C,EAAOjhC,WAC7C,IAAIo3B,EAAM6H,YAAagC,EAAOjhC,YAEtC,IAAK,cACH,MAAO,IAAKo3B,EAAOjwC,QAAS85C,EAAO95C,SACrC,IAAK,sBACH,MAAO,IAAKiwC,EAAO4I,gBArEO11B,EAqEyB22B,EAAO32B,MApE7C,QAAVA,EAAkB,MAAQA,IAqE/B,IAAK,eACH,MAAO,IAAK8sB,EAAOta,SAAUmkB,EAAOnkB,UACtC,IAAK,aACH,MAAO,IAAKsa,EAAO6I,YAAagB,EAAOtkC,KAAMujC,SAAU,MACzD,IAAK,gBACH,MAAO,IAAK9I,EAAO6I,YAAagB,EAAO10C,QAAQgY,KAAO,KAAM27B,SAAUe,EAAO10C,QAC/E,IAAK,WACH,MAAO,IAAK6qC,EAAO+I,aAAcc,EAAOnjC,MAC1C,IAAK,sBACH,MAAO,IAAKs5B,EAAOgJ,eAAgBa,EAAOG,OAC5C,IAAK,sBACH,MAAO,IACFhK,EACHuJ,SAAU,CACRC,cAAeK,EAAOL,cACtBC,cAAeI,EAAOJ,cACtBC,kBAAmBG,EAAOH,kBAC1BC,iBAAkBE,EAAOF,iBACzB78B,KAAM+8B,EAAO/8B,OAGnB,IAAK,uBACH,MAAO,IAAKkzB,EAAOxlB,gBAAiBqvB,EAAOtxB,QAC7C,IAAK,cACH,MAAO,IAAKynB,EAAOiJ,oBAAqBjJ,EAAMiJ,oBAChD,IAAK,YACH,MAAO,IAAKjJ,EAAOkJ,MAAOlJ,EAAMkJ,MAClC,IAAK,uBACH,MAAO,IAAKlJ,EAAOmJ,gBAAiBU,EAAOnjD,SAC7C,IAAK,sBAIH,MAAO,IACFs5C,EACHoJ,aAAc,CAAEC,SAAS,EAAMxtC,KAAM,MACrCytC,OAAQ,IAAKtJ,EAAMsJ,OAAQD,SAAS,IAExC,IAAK,yBACH,MAAO,IACFrJ,EACHoJ,aAAc,CAAEC,SAAS,EAAOxtC,KAAMguC,EAAOhuC,OAEjD,IAAK,uBACH,MAAO,IACFmkC,EACHoJ,aAAc,CAAEC,SAAS,EAAOxtC,KAAM,OAE1C,IAAK,eACH,MAAO,IACFmkC,EACHoJ,aAAc,IAAKpJ,EAAMoJ,aAAcC,SAAS,GAChDC,OAAQ,CAAED,SAAS,EAAMpoC,KAAM,KAAM4L,OAAO,EAAO+I,UAAW,KAAM9I,KAAM,OAE9E,IAAK,kBACH,MAAO,IACFkzB,EACHsJ,OAAQ,CACND,SAAS,EACTpoC,KAAM4oC,EAAO5oC,KACb4L,OAAO,EACP+I,UAAW,KACX9I,KAAM,OAGZ,IAAK,eACH,MAAO,IAAKkzB,EAAOsJ,OAAQ,IAAKtJ,EAAMsJ,OAAQz8B,OAAO,EAAMC,KAAM,OACnE,IAAK,mBACH,MAAO,IACFkzB,EACHsJ,OAAQ,IAAKtJ,EAAMsJ,OAAQz8B,OAAO,EAAO+I,UAAWi0B,EAAOj0B,UAAW9I,KAAM,OAEhF,IAAK,cACH,MAAO,IACFkzB,EACHsJ,OAAQ,IAAKtJ,EAAMsJ,OAAQz8B,OAAO,EAAO+I,UAAW,KAAM9I,KAAM+8B,EAAO/8B,OAE3E,IAAK,gBACH,MAAO,IACFkzB,EACHsJ,OAAQ,CAAED,SAAS,EAAOpoC,KAAM,KAAM4L,OAAO,EAAO+I,UAAW,KAAM9I,KAAM,OAE/E,QACE,OAAOkzB,EAzJN,IAAyB9sB,CA2JhC,CAWA,MAAM+2B,IAAejsC,EAAAA,EAAAA,eAAwC,MAEtD,SAASksC,GAAc,G,IAAA,SAAEhsC,GAAF,EAC5B,MAAO8hC,EAAOmK,IAAYC,EAAAA,EAAAA,YAAWR,GAAShC,IAC9C,OAAO,SAACqC,GAAahrC,SAAQ,CAAC3L,MAAO,CAAE0sC,QAAOmK,Y,SAAajsC,GAC7D,CAEO,SAASmsC,KACd,MAAMlrC,GAAMC,EAAAA,EAAAA,YAAW6qC,IACvB,IAAK9qC,EAAK,MAAM,IAAIrN,MAAM,8CAC1B,OAAOqN,CACT,CAGO,SAASmrC,GAAetK,EAAmBp3B,GAChD,OAAOo3B,EAAM6H,YAAYhyC,SAAS+S,EACpC,CC1WO,SAAS2hC,GAAYC,GAC1B,MAAO1iB,EAAS+Q,IAAcjyC,EAAAA,EAAAA,UAA2C,OAClEk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChC8L,EAAOomC,IAAYlyC,EAAAA,EAAAA,UAAwB,MAiClD,OA/BAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAyBhB,MAvBcnK,WACZ,IACE,MAAM,KAAE0R,SpDqfsDpE,EoDrfvB,CACrCsB,MAAO,CAAE60C,WAAYD,EAAWpvB,MAAO,OpDqfrC9mB,GAASoD,QAAUA,GAAQrG,IAAqD,CACpFuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,oBACFsI,KoD7fH,GAAInD,EAAW,OAEf,MAAM4nC,EAAKrgC,GAAM+iB,QACjB,IAAKsd,GAAIhR,UAAUh7B,OAEjB,YADA8rC,EAAW,MAIb,MAAMG,EAAcnR,GAAiBkR,GACrCF,EAAWG,EACb,CAAE,MACK7nC,GAAW2nC,EAAS,aAAa0R,iCACxC,CAAE,QACKr5C,GAAW4qB,GAAW,EAC7B,CpDoeyB,IAAuCznB,GoDjelEpN,GACO,KACLiK,GAAY,IAEb,CAACq5C,IAEG,CAAE1iB,UAAShM,UAASppB,QAC7B,C,cC7BA,MAAM6wB,GAAQ,IAAI7zB,IACZg7C,GAAW,IAAIh7C,IAad1I,eAAe2jD,GACpBC,G,IACA13B,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA+B,MAE/B,MAAM1iB,EAdR,SAAkBo6C,EAAuB13B,GACvC,MAAO,GAAG03B,KAAiB13B,GAC7B,CAYc0S,CAASglB,EAAe13B,GAC9ByH,EAAS4I,GAAMlyB,IAAIb,GACzB,GAAImqB,GAAU5mB,KAAKoU,MAAQwS,EAAOpB,UAlBrB,IAoBX,OADA9D,GAAI,4BACGkF,EAAO3uB,IAEhB,MAAM4uB,EAAW8vB,GAASr5C,IAAIb,GAC9B,GAAIoqB,EAAU,OAAOA,EAErB,MAAMS,EAAU3F,GACd,qBACA,IACEhe,EAAOrG,IAAoC,CACzCrF,IAAK,qBAAqBsF,mBAAmBs5C,wBAC7Ch1C,MAAO,CAAEsd,SACThb,SAAU,CAAC,CAAEC,GAAI,SAAU/E,KAAM,YAAapD,KAAM,WACpD4I,aAAc,SAElB,CAAEuU,IAAKy9B,EAAe13B,UAErB5rB,KAAM,I,IAAA,KAAEoR,GAAM,EACb,MAAM1M,EAAM,GAAuCA,KAAO,KAE1D,OADAu3B,GAAMlxB,IAAI7B,EAAK,CAAExE,MAAKutB,UAAWxlB,KAAKoU,QAC/Bnc,IAERqhB,MAAM,KAGLkW,GAAMlxB,IAAI7B,EAAK,CAAExE,IAAK,KAAMutB,UAAWxlB,KAAKoU,QACrC,OAERmF,QAAQ,IAAMo9B,GAAS9tC,OAAOpM,IAGjC,OADAk6C,GAASr4C,IAAI7B,EAAK6qB,GACXA,CACT,CC/COr0B,eAAe6jD,GACpBC,EACAjvC,G,IACAkvC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAY,EACZh8C,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAO,GAEP,GAAI8M,EAAK9O,OAAS,EAAG,MAAO,GAE5B,MAAOi+C,EAAUC,GAAYpvC,EAAK,IAC3BqvC,EAAQC,GAAUtvC,EAAK,GAGxBuvC,EAAgBC,GAAkBJ,EAAUD,EAAUG,EAAQD,GAC9DI,EAAa5hD,KAAKkX,IAAI,EAAGlX,KAAKkiC,KAAKwf,EAAgBL,IAEnDQ,EAA8B,GAEpC,IAAK,IAAIl/C,EAAI,EAAGA,GAAKi/C,EAAYj/C,IAAK,CACpC,MAAMgpB,EAAIhpB,EAAIi/C,EACRljB,EAAM4iB,EAAW31B,GAAK61B,EAASF,GAC/Bv5B,EAAMw5B,EAAW51B,GAAK81B,EAASF,GAC/BO,EAAWn2B,EAAI+1B,EAErB,IACE,MAAMn3C,QAAew3C,EAAAA,GAAAA,IAAeX,EAAQ,CAAEY,SAAUj6B,EAAKk6B,UAAWvjB,GAAOr5B,GACzE68C,EAAM33C,EAAOlH,OAAS,EAAIkH,EAAO,GAAK,KAC5Cs3C,EAAOp/C,KAAK,CACVq/C,SAAU9hD,KAAK4c,MAAiB,GAAXklC,GAAiB,GACtC5kB,MAAc,MAAPglB,GAAet+C,OAAOi4B,SAASqmB,GAAOA,EAAM,MAEvD,CAAE,MACAL,EAAOp/C,KAAK,CAAEq/C,SAAU9hD,KAAK4c,MAAiB,GAAXklC,GAAiB,GAAI5kB,MAAO,MACjE,CACF,CAEA,OAAO2kB,CACT,CAyBA,SAASM,GAAY3qC,EAAWC,EAAWF,GACzC,IAAI6qC,GAAS,EACb,IAAK,IAAIz/C,EAAI,EAAGE,EAAI0U,EAAKlU,OAAS,EAAGV,EAAI4U,EAAKlU,OAAQR,EAAIF,IAAK,CAC7D,MAAM0/C,EAAK9qC,EAAK5U,GAAG,GACjB2/C,EAAK/qC,EAAK5U,GAAG,GACT4/C,EAAKhrC,EAAK1U,GAAG,GACjB2/C,EAAKjrC,EAAK1U,GAAG,GACXy/C,EAAK7qC,GAAM+qC,EAAK/qC,GAAKD,GAAM+qC,EAAKF,IAAO5qC,EAAI6qC,IAAQE,EAAKF,GAAMD,IAChED,GAAUA,EAEd,CACA,OAAOA,CACT,CAMO,SAASK,GAAe/jB,EAAa3W,EAAauW,GACvD,MAAM3nB,EAAO2nB,EAAQC,SACrB,MAAkB,YAAd5nB,EAAKrQ,KACA67C,GAAYzjB,EAAK3W,EAAKpR,EAAKC,YAAY,IAE9B,iBAAdD,EAAKrQ,MACAqQ,EAAKC,YAAYrU,KAAMmgD,GAASP,GAAYzjB,EAAK3W,EAAK26B,EAAK,IAGtE,CAEO,SAASf,GAAkBgB,EAAcC,EAAcC,EAAcC,GAC1E,MACMC,GAASF,EAAOF,GAAQ3iD,KAAKqX,GAAM,IACnC2rC,GAASF,EAAOF,GAAQ5iD,KAAKqX,GAAM,IACnC1V,EACJ3B,KAAK6X,IAAIkrC,EAAO,IAAM,EACtB/iD,KAAK4X,IAAI,EAAQ5X,KAAKqX,GAAM,KAAOrX,KAAK4X,IAAI,EAAQ5X,KAAKqX,GAAM,KAAOrX,KAAK6X,IAAImrC,EAAO,IAAM,EAC9F,OAAOC,QAAQjjD,KAAKkjD,MAAMljD,KAAKmjD,KAAKxhD,GAAI3B,KAAKmjD,KAAK,EAAIxhD,GACxD,CAaA,MAAMyhD,GAAS,CAAC,MAAO,MAAO,OAiB9B,SAASC,GAAiB/kB,GACxB,MAAMziB,EAAOyiB,EAAQkT,YAAY31B,KAC3Bzc,EAAQk/B,EAAQkT,YAAc,CAAC,EACrC,MAAO,CACL8R,QAAyB,iBAATznC,EAAoBA,EAAO,KAC3C0nC,QAAS,CACP57B,IAAgC,iBAApBvoB,EAAM4yC,UAAyB5yC,EAAM4yC,UAAY,KAC7DpqB,IAAgC,iBAApBxoB,EAAMokD,UAAyBpkD,EAAMokD,UAAY,KAC7D37B,IAAgC,iBAApBzoB,EAAM6yC,UAAyB7yC,EAAM6yC,UAAY,MAGnE,CAEA,SAASwR,GAAenlB,GAItB,MAAMolB,EAAQ,EAASlS,YAAYA,YAAsD,CAAC,EACpFL,EAAYuS,EAAMvS,UACxB,MAAO,CACLmS,QAA8B,iBAAdnS,EAAyBA,EAAY,KACrDoS,QAAS,CACP57B,IAA0B,iBAAd+7B,EAAM/7B,IAAmB+7B,EAAM/7B,IAAM,KACjDC,IAA0B,iBAAd87B,EAAM97B,IAAmB87B,EAAM97B,IAAM,KACjDC,IAA0B,iBAAd67B,EAAM77B,IAAmB67B,EAAM77B,IAAM,MAGvD,CAgBOvqB,eAAeqmD,GACpBz7B,EACAq2B,G,IACAqF,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA6C,KAC7Cv+C,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAO,GAEP,MAAMw+C,EAAsC37B,EAAQpe,IAAK,I,IAAA,IAAE40B,EAAG,IAAE3W,GAAK,EACnE,GAAIw2B,EACF,IAAK,MAAMjgB,KAAWigB,EAAalgB,SACjC,GAAIokB,GAAe/jB,EAAK3W,EAAKuW,GAAU,OAAO+kB,GAAiB/kB,GAGnE,GAAIslB,EACF,IAAK,MAAMtlB,KAAWslB,EAASvlB,SAC7B,GAAIokB,GAAe/jB,EAAK3W,EAAKuW,GAAU,OAAOmlB,GAAenlB,GAGjE,OAAO,OAGHwlB,EAAQ,IAAI/gD,IAClB,IAAK,MAAMxF,KAAKsmD,EACVtmD,GAAG+lD,SAASQ,EAAMl/C,IAAIrH,EAAE+lD,SAG9B,MAAMS,EAAiB,IAAI/9C,UACrBmK,QAAQ2hB,IACZlmB,MAAM43B,KAAKsgB,GAAOE,QAASnoC,GACzBunC,GAAOt5C,IAAIxM,MAAOksB,IAChB,MAAMlnB,QAAY2+C,GAAmBplC,EAAM2N,GAC3Cu6B,EAAep7C,IAAI,GAAGkT,KAAQ2N,IAASlnB,OAK7C,MAAM2hD,EAAmD,CACvDt8B,IAAK,IAAI/b,MAAMsc,EAAQ7kB,QAAQsV,KAAK,MACpCiP,IAAK,IAAIhc,MAAMsc,EAAQ7kB,QAAQsV,KAAK,MACpCkP,IAAK,IAAIjc,MAAMsc,EAAQ7kB,QAAQsV,KAAK,OAgCtC,aA7BMxI,QAAQ2hB,IACZ5J,EAAQ87B,QAAQ,CAAC1rC,EAAIQ,KACnB,MAAMvb,EAAIsmD,EAAS/qC,GACnB,OAAKvb,EAEE6lD,GAAOt5C,IAAIxM,MAAOksB,IACvB,MAAM06B,EAAS3mD,EAAEgmD,QAAQ/5B,GACnBlnB,EAAM/E,EAAE+lD,QAAWS,EAAep8C,IAAI,GAAGpK,EAAE+lD,WAAW95B,MAAY,KAAQ,KAEhF,GAAKlnB,EAKL,IACE,MAAMiI,QAAew3C,EAAAA,GAAAA,IAAez/C,EAAK,CAAE0/C,SAAU1pC,EAAGyP,IAAKk6B,UAAW3pC,EAAGomB,KAAOr5B,GAC5E68C,EAAM33C,EAAOlH,OAAS,EAAIkH,EAAO,GAAK,KACjC,MAAP23C,GAAet+C,OAAOi4B,SAASqmB,GACjC+B,EAAez6B,GAAO1Q,GAAOopC,EAE7B+B,EAAez6B,GAAO1Q,GAAOorC,CAEjC,CAAE,MACAD,EAAez6B,GAAO1Q,GAAOorC,CAC/B,MAdED,EAAez6B,GAAO1Q,GAAOorC,IAPlB,MA0BZh8B,EAAQpe,IAAI,CAAC25B,EAAG3qB,KAAS,CAC9B6O,IAAKs8B,EAAet8B,IAAI7O,GACxB8O,IAAKq8B,EAAer8B,IAAI9O,GACxB+O,IAAKo8B,EAAep8B,IAAI/O,KAE5B,CCrOA,MACMqrC,GAAa,IASnB,SAASC,GAA2BhlD,GAClC,MAAMilD,EAAWjlD,EAAMilD,UAAY,CAAC,EAC9BC,EAAWllD,EAAMklD,UAAY,CAAC,EAC9BC,EAAQF,EAAS,IAAMp7C,OAAOsB,OAAO85C,GAAU,GAC/CG,EAAQF,EAAS,IAAMr7C,OAAOsB,OAAO+5C,GAAU,GACrD,IAAKC,GAAOpsC,QAAUqsC,GAAOrsC,MAAO,OAAO,KAE3C,MAAMsO,EAAS89B,EAAMpsC,MACfuO,EAAS89B,EAAMrsC,OACdqQ,EAAMC,GAAQhC,EAAOulB,SACtB/0B,GAAMuR,EAAOC,GAAQ,EAE3B,OACE,SAACjC,GAAWA,CACVC,OAASjP,GAAMiP,EAAOjP,GACtBkP,OAASjP,GAAMiP,EAAOjP,GACtBkP,QAAS1P,GAGf,CAiBA,MAAM21B,GAA2B,CAC/B1P,MAAO,CACL9gB,MAAO,iBACP9C,MAAO,WAETmrC,SAAU,CACRroC,MAAO,yBACP9C,MAAO,WAETqO,IAAK,CACHvL,MAAO,MACP9C,MAAO,WAITuO,IAAK,CACHzL,MAAO,OACP9C,MAAO,YAII,SAASorC,GAAmB,G,IAAA,SACzC5kC,EAAQ,YACR6kC,EAAW,aACXpG,EAAY,OACZqG,GAJyC,EAMzC,MAAM,MAAEtO,EAAK,SAAEmK,GAAaE,MACrBkE,EAAWC,IAAgB5nD,EAAAA,EAAAA,UAAuB,KAClDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAKhCoZ,EAAQyuC,IAAa7nD,EAAAA,EAAAA,UAhFP,KAiFf8nD,GAAUrlB,EAAAA,EAAAA,QAAoB,MAC9B1vB,GAAQ0vB,EAAAA,EAAAA,QAA6C,OAEpDslB,EAAOC,IAAYhoD,EAAAA,EAAAA,UAAS,GAC7BioD,EAAYF,EAAQ,EAAIjlD,KAAKkX,IAAIitC,GAAYc,EAlFnC,KAkFwD,KAElEG,GAAUrwC,EAAAA,EAAAA,aAAa0vB,IAC3B,MAAM4gB,EAAUL,EAAQliB,SAASwiB,eAAeC,cAAgB,EAC1DruC,EAAMmuC,EAAU,EAAIrlD,KAAKkX,IAAIitC,GAAYkB,EAtFjC,KAsFwDtqB,IACtE,OAAO/6B,KAAK4c,MAAM5c,KAAK8S,IAAIoE,EAAKlX,KAAKkX,IAAIitC,GAAY1f,MACpD,IAEG+gB,IAAclP,EAAMoJ,aAAavtC,MAGvChV,EAAAA,EAAAA,WAAU,KACR,MAAMsoD,EAAQT,EAAQliB,SAASwiB,cAC/B,IAAKG,EAAO,OACZ,MAAMz5B,EAAO,KACXk5B,EAASO,EAAMF,cACfR,EAAWtgB,GAAM2gB,EAAQ3gB,KAE3BzY,IACA,MAAM05B,EAAY,IAAIC,eAAe35B,GAErC,OADA05B,EAAUh3B,QAAQ+2B,GACX,IAAMC,EAAUE,cACtB,CAACR,EAASI,IAEb,MAAMK,GAAe9wC,EAAAA,EAAAA,aAClB1F,IACCA,EAAEm/B,iBACFv+B,EAAM6yB,QAAU,CAAErrB,EAAGpI,EAAEy2C,QAASxvC,UAC/BjH,EAAE02C,OAAuBC,oBAAoB32C,EAAE42C,YAElD,CAAC3vC,IAEG4vC,GAAUnxC,EAAAA,EAAAA,aACb1F,IACMY,EAAM6yB,SACXiiB,EAAUK,EAAQn1C,EAAM6yB,QAAQxsB,OAASrG,EAAM6yB,QAAQrrB,EAAIpI,EAAEy2C,WAE/D,CAACV,IAEGe,GAAepxC,EAAAA,EAAAA,aAAY,KAC/B9E,EAAM6yB,QAAU,MACf,KAKH3lC,EAAAA,EAAAA,WAAU,KACR,MAAMipD,EAAQC,sBAAsB,IAAMzB,EAAO9hB,SAASwjB,UAC1D,MAAO,IAAMC,qBAAqBH,IACjC,CAAC9vC,EAAQsuC,KACZznD,EAAAA,EAAAA,WACE,IAAM,KACJkpD,sBAAsB,IAAMzB,EAAO9hB,SAASwjB,WAE9C,CAAC1B,IAQH,MAAQxmB,QAASooB,GAAqB3F,GAAY,aAC1CziB,QAASqoB,GAA0B5F,GAAY,kBAC/CziB,QAASsoB,GAAuB7F,GAAY,eAC5CziB,QAASuoB,GAAkB9F,GAAY,SAEzC+F,GAAgB7/C,EAAAA,EAAAA,SAA0C,KAC9D,MAAM8/C,EAAc,CAClBL,EACAC,EACAC,EACAC,GACAxlD,OAAQsf,GAA2C,MAALA,GAChD,OAA2B,IAAvBomC,EAAYxjD,OAAqB,KAC9B,CACLiD,KAAM,oBACN+3B,SAAUwoB,EAAY7C,QAASvjC,GAAMA,EAAE4d,YAExC,CAACmoB,EAAkBC,EAAuBC,EAAoBC,KAE1DG,EAAkBC,IAAuB7pD,EAAAA,EAAAA,UAE9C,KAKFC,EAAAA,EAAAA,WAAU,KACR,MAAMgV,EAAOmkC,EAAMoJ,aAAavtC,KAChC,IAAKA,GAAQA,EAAK9O,OAAS,GAA0B,IAArBwhD,EAAUxhD,SAAiBk7C,EAEzD,YADAwI,EAAoB,IAGtB,IAAIt/C,GAAY,EAIhBs/C,EAAoB,IAEpB,MAAOzF,EAAUC,GAAYpvC,EAAK,IAC3BqvC,EAAQC,GAAUtvC,EAAK,GACxBuvC,EAAgBC,GAAkBJ,EAAUD,EAAUG,EAAQD,GAO9Dt5B,EAAkC28B,EAAU/6C,IAAKwO,IACrD,MAAMqT,EAAI+1B,EAAgB,EAAIppC,EAAGwpC,SAAWJ,EAAgB,EAC5D,MAAO,CACLhjB,IAAK4iB,EAAW31B,GAAK61B,EAASF,GAC9Bv5B,IAAKw5B,EAAW51B,GAAK81B,EAASF,MASlC,MALC,WACC,MAAMyF,QAAerD,GAA+Bz7B,EAASq2B,EAAcqI,GACtEn/C,GAAWs/C,EAAoBC,EACrC,EAHA,GAKM,KACLv/C,GAAY,IAEb,CAACo9C,EAAWvO,EAAMoJ,aAAavtC,KAAMosC,EAAcqI,IAEtD,MAAM3iC,GAAYld,EAAAA,EAAAA,SAAsB,IAClC+/C,EAAiBzjD,SAAWwhD,EAAUxhD,OACjCwhD,EAAU/6C,IAAKwO,IAAQ,IAAKA,EAAIqP,IAAK,KAAMC,IAAK,KAAMC,IAAK,QAE7Dg9B,EAAU/6C,IAAI,CAACwO,EAAI3V,KAAO,IAAK2V,KAAOwuC,EAAiBnkD,MAC7D,CAACkiD,EAAWiC,KAIf3pD,EAAAA,EAAAA,WAAU,KACR,IAAKm5C,EAAMoJ,aAAavtC,MAA4B,IAApB2N,EAASzc,OAAc,OACvD,IAAIoE,GAAY,EA6DhB,MA3DC,WACC4qB,GAAW,GACX,IACE,MACM40B,EADarG,GAAetK,EAAO,OACFqO,EAAc,IAE9CuC,EAAgBC,SAAoBh3C,QAAQ2hB,IAAI,CACrD3hB,QAAQ2hB,IAAIhS,EAAShW,IAAKyS,GAAOsW,GAAUtW,EAAGla,MAC9C8N,QAAQ2hB,IAAIm1B,EAAkBn9C,IAAKyS,GAAOsW,GAAUtW,EAAGla,QAEnD+kD,EAAcF,EAAe/lD,OAAQ0T,GAAwB,MAALA,GACxDwyC,EAAUF,EAAWhmD,OAAQ0T,GAAwB,MAALA,GAEtD,GAA2B,IAAvBuyC,EAAY/jD,QAAgBoE,EAE9B,YADAq9C,EAAa,IAIf,MAAOwC,EAAeC,SAAmBp3C,QAAQ2hB,IAAI,CACnD3hB,QAAQ2hB,IACNs1B,EAAYt9C,IAAKxH,GAAQ6+C,GAAmB7+C,EAAKg0C,EAAMoJ,aAAavtC,KAAO,KAE7EhC,QAAQ2hB,IAAIu1B,EAAQv9C,IAAKxH,GAAQ6+C,GAAmB7+C,EAAKg0C,EAAMoJ,aAAavtC,KAAO,OAErF,GAAI1K,EAAW,OAGf,MAAM+/C,EACJF,EAAc,IAAIx9C,IAAI,CAACwO,EAAI3V,KAAO,IAC7B2V,EACH4kB,MAAOoqB,EAAcpnB,OACnB,CAACunB,EAAKC,IAAWD,GAAOC,EAAO/kD,IAAIu6B,OAAS,KAC5C,UAEG,GAGHyqB,EAAkCH,EAAc19C,IAAI,CAAC25B,EAAG9gC,KAC5D,IAAK,MAAM+kD,KAAUH,EAAW,CAC9B,MAAMpjD,EAAIujD,EAAO/kD,IAAIu6B,MACrB,GAAS,MAAL/4B,EAAW,OAAOA,CACxB,CACA,OAAO,OAIHyjD,EC5SP,SACLC,EACAN,GAEA,OAAyB,IAArBA,EAAUlkD,OAAqBwkD,EAC5BA,EAAK/9C,IAAI,CAACwO,EAAI3V,KACnB,IAAK,MAAM+kD,KAAUH,EAAW,CAC9B,MAAM9C,EAAWiD,EAAO/kD,IAAIu6B,MAC5B,GAAgB,MAAZunB,EACF,MAAO,IAAKnsC,EAAI4kB,MAAOunB,EAE3B,CACA,OAAOnsC,GAEX,CD8RuBwvC,CAAiBN,EAAeD,GAAWz9C,IAAI,CAACwO,EAAI3V,KAAO,IACrE2V,EACHmsC,SAAUkD,EAAahlD,MAGzBmiD,EAAa8C,EACf,CAAE,MACKngD,GAAWq9C,EAAa,GAC/B,CAAE,QACKr9C,GAAW4qB,GAAW,EAC7B,CACD,EAzDA,GA2DM,KACL5qB,GAAY,IAEb,CAAC6uC,EAAMoJ,aAAavtC,KAAM2N,EAAU6kC,EAAarO,EAAM6H,cAG1D,MAAO4J,EAAOC,IAAY9qD,EAAAA,EAAAA,UAAkC,MAEtD6wC,GAAchnC,EAAAA,EAAAA,SAA0B,KAC5C,MAAM2hB,EAASzE,EAAU9iB,OAAQ4V,GAAiB,MAAXA,EAAEmmB,OAAepzB,IAAKiN,GAAMA,EAAEmmB,OACrE,OAAsB,IAAlBxU,EAAOrlB,OAAqB,EAAE,GAAI,GAE/B,CADKrD,KAAK8S,OAAO4V,GACV,EAAGK,KAChB,CAAC9E,IAEEoqB,EAAU0Z,GAASha,EAEnBka,GAAelzC,EAAAA,EAAAA,aAClB1F,IACCA,EAAEm/B,iBACF,MAAO0Z,EAAIC,GAAMJ,GAASha,EACpBW,EAAQyZ,EAAKD,EACbE,GAAOF,EAAKC,GAAM,EAClBE,EAASh5C,EAAEs/B,OAAS,EAAI,KAAO,IAC/B2Z,EAAWtoD,KAAKkX,IAAI,EAAGw3B,EAAQ2Z,GACrCL,EAAS,CAACI,EAAME,EAAW,EAAGF,EAAME,EAAW,KAEjD,CAACP,EAAOha,IAGJwa,GAAYxzC,EAAAA,EAAAA,aAAY,IAAMizC,EAAS,MAAO,IAKpD,IAFA7qD,EAAAA,EAAAA,WAAU,IAAM6qD,EAAS,MAAO,CAACnD,KAE5BvO,EAAMoJ,aAAavtC,KAAM,OAAO,KAErC,MAAMq2C,EAAevkC,EAAU9iB,OAAQ4V,GAAiB,MAAXA,EAAEmmB,OAE/C,OACE,UAACkI,UAAAA,CACC7Q,IAAKywB,EACLvuC,aAAW,eACXvX,UAAU,2CACVyK,MAAO,CAAE2M,U,WAGT,SAACuD,MAAAA,CACCrD,KAAK,YACLiyC,mBAAiB,aACjBhyC,aAAW,sBACXiyC,gBAAepyC,EACfqyC,gBAAexE,GACfyE,gBAAezD,QAAar6C,EAC5B+9C,SAAU,EAGV3pD,UAAU,oRACV4pD,cAAejD,EACfkD,cAAe7C,EACf8C,YAAa7C,EACb8C,gBAAiB9C,EACjB+C,UAAY75C,IACI,YAAVA,EAAEvI,KAA+B,cAAVuI,EAAEvI,MAC7BuI,EAAEm/B,iBAEFuW,EAAWtgB,GAAM2gB,EAAQ3gB,GAAe,YAAVp1B,EAAEvI,IA1VvB,Y,UA6VX,SAACmU,OAAAA,CAAK/b,UAAU,uCAGlB,UAAC2a,MAAAA,CAAI3a,UAAU,uD,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAACiqD,GAAAA,EAAQA,CAACjqD,UAAU,kCACpB,SAAC4nC,KAAAA,CAAG5nC,UAAU,sB,SAAsB,iBACnCkzB,IAAW,SAACnX,OAAAA,CAAK/b,UAAU,gC,SAAgC,eAE9D,UAAC2a,MAAAA,CAAI3a,UAAU,4B,UACZ6oD,IACC,SAAC9oD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVic,MAAM,kBACN1E,aAAW,kBACXqG,QAASyrC,E,UAET,SAACa,GAAAA,EAASA,CAAClqD,UAAU,cAGzB,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVuX,aAAW,uBACX0E,MAAM,uBACN2B,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,yB,UAEhC,SAAC+iD,GAAAA,EAACA,CAACnqD,UAAU,oBAMnB,SAAC2a,MAAAA,CAAI3a,UAAU,gCAAgCqvC,QAAS0Z,E,SACrDO,EAAanlD,OAAS,GACrB,SAAC8lC,GAAcA,CAAC37B,OAAQo/B,GAAa1tC,UAAU,gB,UAC7C,UAAC8tC,GAAAA,EAAaA,CAACh+B,KAAMw5C,EAAcpgC,OAAQ,CAAE6kB,IAAK,GAAIC,MAAO,GAAIC,OAAQ,EAAGtJ,KAAM,I,WAChF,UAAC3a,OAAAA,C,WAEC,UAACC,iBAAAA,CAAe9mB,GAAG,gBAAgB+kB,GAAG,IAAIE,GAAG,IAAID,GAAG,IAAIE,GAAG,I,WACzD,SAAC6B,OAAAA,CAAKC,OAAO,KAAKC,UAAU,UAAUC,YAAa,OACnD,SAACH,OAAAA,CAAKC,OAAO,OAAOC,UAAU,UAAUC,YAAa,UAGvD,UAACJ,iBAAAA,CAAe9mB,GAAG,eAAe+kB,GAAG,IAAIE,GAAG,IAAID,GAAG,IAAIE,GAAG,I,WACxD,SAAC6B,OAAAA,CAAKC,OAAO,KAAKC,UAAU,UAAUC,YAAa,OACnD,SAACH,OAAAA,CAAKC,OAAO,OAAOC,UAAU,UAAUC,YAAa,aAGzD,SAACxE,GAAAA,EAAaA,CAACC,gBAAgB,SAE/B,SAAC8pB,GAAAA,EAAaA,CACZxnB,GAAI+mB,EAAQ,GACZ9mB,GAAI,EACJ5O,KAAK,sBACLc,YAAa,EACbs1B,WAAW,YAEb,SAAC9pB,GAAAA,EAAKA,CACJC,QAAQ,WACR5e,KAAK,SACL8mC,cAAgB/rC,GAAc,IAAG1B,EAAAA,EAAAA,GAAM0B,OACvC8jB,SAAU,MAEZ,SAACC,GAAAA,EAAKA,CACJ4mB,OAAQqC,EACRjB,cAAgB/rC,IAAc1B,EAAAA,EAAAA,GAAM0B,EAAG,GACvC8jB,SAAU,GACV/I,MAAO,CACLxS,MAAO,QACPyb,OAAQ,GACRC,SAAU,aACV3b,MAAO,CAAEwb,SAAU,QAGvB,SAAC4kB,GAAYA,CACXsD,SACE,SAACrD,GAAmBA,CAClBxkB,UAAW,CAAC5b,EAAOF,KACjB,MAAM0S,EAAQwwB,GAAYljC,IAAmC0S,OAAS1S,EACtE,OAAa,MAATE,EAAsB,GAAGwS,OACtB,GAAGA,OAAUzc,EAAAA,EAAAA,GAAMiK,EAAiB,iBAMnD,SAAC0/C,GAAAA,EAAIA,CACHhjD,KAAK,WACL4e,QAAQ,QACRtM,OAAO,UACPD,KAAK,qBACLc,YAAa,EACbZ,YAAa,EACb40B,KAAK,EACL7nB,mBAAmB,EACnB2jC,UAAWlb,EAAQ,MAGrB,SAACY,GAAAA,EAAaA,CACZx3B,EAAG,EACHmB,OAAO,UACPC,YAAa,IACbmM,gBAAgB,MAChB5I,MAAO,CAAExS,MAAO,MAAO0b,SAAU,QAAS3M,KAAM,UAAWwM,SAAU,IACrE4pB,WAAW,YAEb,SAACxB,GAAAA,EAAIA,CACHjnC,KAAK,WACL4e,QAAQ,WACRtM,OAAO,UACPC,YAAa,IACbmM,gBAAgB,MAChByoB,KAAK,EACLE,cAAc,EACdjkC,KAAK,iBACLkc,mBAAmB,KAErB,SAAC2nB,GAAAA,EAAIA,CACHjnC,KAAK,WACL4e,QAAQ,MACRtM,OAAQ8O,GAAiBC,IACzB3C,gBAAgB,MAChBnM,YAAa,IACb40B,KAAK,EACLE,cAAc,EACdjkC,KAAK,MACLkc,mBAAmB,KAErB,SAAC2nB,GAAAA,EAAIA,CACHjnC,KAAK,WACL4e,QAAQ,MACRtM,OAAQ8O,GAAiBG,IACzB7C,gBAAgB,MAChBnM,YAAa,IACb40B,KAAK,EACLE,cAAc,EACdjkC,KAAK,OACLkc,mBAAmB,KAGrB,SAAC4jC,GAAAA,EAAUA,CAACC,UAAWrF,WAI3B,SAACvqC,MAAAA,CAAI3a,UAAU,0C,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,gC,SACVkzB,EACG,kCACA,sDAOlB,C,gDEphBe,SAASs3B,GAAW,G,IAAA,OAAE9E,EAAM,cAAE+E,GAAV,EACjC,MAAM,MAAErT,EAAK,SAAEmK,GAAaE,KAqC5B,OACE,SAACnM,GAAeA,CAACC,cAAe,I,UAG9B,UAAC56B,MAAAA,CACCrD,KAAK,UACLC,aAAW,kBACXgyC,mBAAiB,WACjBvpD,UAAU,2F,WAEV,SAAC0qD,GAAAA,CACCxtC,MAAM,eACN07B,QAASxB,EAAMoJ,aAAaC,QAAU,yBAA2B,uBACjE7iC,QAjCmB,KACrBw5B,EAAMoJ,aAAaC,QACrBc,EAAS,CAAEn6C,KAAM,yBAEjBm6C,EAAS,CAAEn6C,KAAM,yBA8BbvH,MAAM,SAACoqD,GAAAA,EAAQA,CAACjqD,UAAU,gBAC1BgjB,OAAQo0B,EAAMoJ,aAAaC,WAE7B,SAACiK,GAAAA,CACCxtC,MAAM,eACN07B,QACExB,EAAMsJ,OAAOD,QACT,iCACA,0DAEN7iC,QApCa,KACfw5B,EAAMsJ,OAAOD,QACfc,EAAS,CAAEn6C,KAAM,mBAEjBm6C,EAAS,CAAEn6C,KAAM,iBACjBqjD,QAgCI5qD,MAAM,SAAC8qD,GAAAA,EAAOA,CAAC3qD,UAAU,gBACzBgjB,OAAQo0B,EAAMsJ,OAAOD,WAEvB,SAACiK,GAAAA,CACCxtC,MAAM,KACN07B,QAASxB,EAAMkJ,KAAO,cAAgB,cACtC1iC,QAlEe,KACrB,MAAMhT,EAAM86C,EAAO9hB,SAASgnB,SACvBhgD,IAEL22C,EAAS,CAAEn6C,KAAM,cAEZgwC,EAAMkJ,KAKT11C,EAAIigD,OAAO,CAAEC,MAAO,EAAGC,QAAS,EAAGt8B,SAAU,MAH7C7jB,EAAIigD,OAAO,CAAEC,MAAO,GAAIr8B,SAAU,QA2D9B5uB,MAAM,SAACmrD,GAAAA,EAAGA,CAAChrD,UAAU,gBACrBgjB,OAAQo0B,EAAMkJ,QAEhB,SAAC3lC,MAAAA,CAAI3a,UAAU,0BACf,SAAC0qD,GAAAA,CACC9R,QAAQ,WACRh7B,QAzCa,IAAM8nC,EAAO9hB,SAASgnB,UAAUK,SA0C7CprD,MAAM,SAACqrD,GAAAA,EAAIA,CAAClrD,UAAU,mBAExB,SAAC0qD,GAAAA,CACC9R,QAAQ,YACRh7B,QA7Cc,IAAM8nC,EAAO9hB,SAASgnB,UAAUO,UA8C9CtrD,MAAM,SAACurD,GAAAA,EAAKA,CAACprD,UAAU,sBAKjC,CAEA,SAAS0qD,GAAc,G,IAAA,MACrBxtC,EAAK,QACL07B,EAAO,QACPh7B,EAAO,KACP/d,EAAI,OACJmjB,GALqB,EAkBrB,OACE,UAACqD,GAAOA,C,WACN,SAACovB,GAAcA,CAACx1C,SAAO,E,UACrB,SAACF,EAAAA,EAAMA,CACLb,QAAS8jB,EAAS,UAAY,QAC9BvjB,KAAK,OACLO,UAAU,qBACVuX,aAAY2F,GAAS07B,EACrBjP,eAAc3mB,EACdpF,QAASA,E,SAER/d,OAGL,SAAC61C,GAAcA,CAACX,KAAK,OAAO/0C,UAAU,U,SACnC44C,MAIT,C,qCCnIe,SAASyS,GAAiB,G,IAAA,OAAE3F,GAAF,EACvC,MAAM,MAAEtO,EAAK,SAAEmK,GAAaE,MACrB6J,EAAYC,IAAiBvtD,EAAAA,EAAAA,UAAkC,OAGtEC,EAAAA,EAAAA,WAAU,KACJm5C,EAAMoJ,aAAaC,SACrB8K,EAAc,OAEf,CAACnU,EAAMoJ,aAAaC,WAGvBxiD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,GAAKhgD,EAEL,OAAIwsC,EAAMoJ,aAAaC,SACrB71C,EAAI4gD,YAAY/gD,MAAMghD,OAAS,YACxB,KACL7gD,EAAI4gD,YAAY/gD,MAAMghD,OAAS,UAHnC,GAMC,CAACrU,EAAMoJ,aAAaC,QAASiF,IAEhC,MAAMgG,GAAc71C,EAAAA,EAAAA,aACjB1F,IACC,IAAKinC,EAAMoJ,aAAaC,QAAS,OAEjC,MAAMkL,EAA0B,CAACx7C,EAAEy7C,OAAOpsB,IAAKrvB,EAAEy7C,OAAO/iC,KAEnDyiC,GAGH/J,EAAS,CACPn6C,KAAM,yBACN6L,KAAM,CAACq4C,EAAYK,KAErBJ,EAAc,OANdA,EAAcI,IASlB,CAACvU,EAAMoJ,aAAaC,QAAS6K,EAAY/J,KAI3CtjD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,IAAQwsC,EAAMoJ,aAAaC,QAAS,OAEzC,MAAMoL,EAAW17C,IACfu7C,EAAYv7C,IAId,OADAvF,EAAIkhD,GAAG,QAASD,GACT,KACLjhD,EAAImhD,IAAI,QAASF,KAElB,CAACnG,EAAQtO,EAAMoJ,aAAaC,QAASiL,KAGxCztD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OAEV,MAAMohD,EAAY,KACZphD,EAAIqhD,SAAS,uBAAuBrhD,EAAIshD,UAAU,sBAClDthD,EAAIqhD,SAAS,yBAAyBrhD,EAAIshD,UAAU,yBAM1D,OAHAthD,EAAIkhD,GAAG,OAAQE,GACfA,IAEO,KACLphD,EAAImhD,IAAI,OAAQC,KAEjB,CAACtG,EAAQtO,EAAMoJ,aAAaC,QAASrJ,EAAMoJ,aAAavtC,OAG3D,MAAMk5C,EAAyC,CAC7C/kD,KAAM,oBACN+3B,SAAU,IAEJiY,EAAMoJ,aAAavtC,KACnB,CACE,CACE7L,KAAM,UACNi4B,SAAU,CACRj4B,KAAM,aACNsQ,YAAa0/B,EAAMoJ,aAAavtC,MAElCq/B,WAAY,CAAC,MAEZ8E,EAAMoJ,aAAavtC,KAAKrI,IAAKwhD,IAAW,CACzChlD,KAAM,UACNi4B,SAAU,CAAEj4B,KAAM,QAAkBsQ,YAAa00C,GACjD9Z,WAAY,CAAC,MAGjB,MAEAgZ,EACA,CACE,CACElkD,KAAM,UACNi4B,SAAU,CACRj4B,KAAM,QACNsQ,YAAa4zC,GAEfhZ,WAAY,CAAC,IAGjB,KAIR,OAAK8E,EAAMoJ,aAAaC,SAAYrJ,EAAMoJ,aAAavtC,MAGrD,UAACo5C,GAAAA,GAAMA,CAAClpD,GAAG,gBAAgBiE,KAAK,UAAU0I,KAAMq8C,E,WAC9C,SAACG,GAAAA,GAAKA,CACJnpD,GAAG,qBACHiE,KAAK,OACLnF,OAAQ,CAAC,KAAM,QAAS,cACxBsqD,MAAO,CACL,aAAc,UACd,aAAc,EACd,iBAAkB,CAAC,EAAG,OAG1B,SAACD,GAAAA,GAAKA,CACJnpD,GAAG,uBACHiE,KAAK,SACLnF,OAAQ,CAAC,KAAM,QAAS,SACxBsqD,MAAO,CACL,gBAAiB,EACjB,eAAgB,UAChB,sBAAuB,UACvB,sBAAuB,QAtBqC,IA2BtE,C,eCxIA,SAASC,GAAO9hD,EAAsB+hD,GACpC,OAAa,MAAT/hD,EAAsB,IACnB,IAAGjK,EAAAA,EAAAA,GAAMiK,EAAO,EAAG+hD,MAC5B,CAWe,SAASC,GAAY,G,IAAA,QAAEC,EAAO,eAAE3M,EAAc,QAAE4M,EAAO,MAAEC,EAAQ,UAA5C,EAClC,MAAMC,EAAUD,EAAMl/C,WAAW,OACjC,OAGE,UAACgN,MAAAA,CACCoyC,cAAU,EACVC,aAAYH,EACZ7sD,WAAWQ,EAAAA,EAAAA,IACT,OACAssD,EAAU,mBAAqB,WAC/BD,EAAM9+C,SAAS,SACX,cACA8+C,EAAM9+C,SAAS,UACb,YACA,gB,WAGR,UAAC4M,MAAAA,CACCrD,KAAK,SACLC,aAAW,qBACXvX,UAAU,8E,WAEV,UAAC2a,MAAAA,CAAI3a,UAAU,0C,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,oC,SACb2sD,EAAWA,EAAQM,MAAQ,mBAAsB,mBAEpD,SAACvjB,SAAAA,CACCtiC,KAAK,SACLmQ,aAAW,uBACX0E,MAAM,uBACN2B,QAASgvC,EACT5sD,UAAU,iH,UAEV,SAACmqD,GAAAA,EAACA,CAACnqD,UAAU,kBAGhB2sD,IAAW,SAACO,GAAAA,CAAYP,QAASA,EAAS3M,eAAgBA,QAI7D,UAACrlC,MAAAA,CACC6uB,eAAW,EACXxpC,WAAWQ,EAAAA,EAAAA,IAAG,wBAAyBssD,EAAU,mBAAqB,Y,WAEtE,SAAC/wC,OAAAA,CAAK/b,UAAU,+BAChB,SAAC+b,OAAAA,CAAK/b,UAAU,qGAIxB,CAEA,SAASktD,GAAY,G,IAAA,QACnBP,EAAO,eACP3M,GAFmB,EAOnB,MAAM,QAAEmN,EAAO,SAAEC,EAAQ,aAAEC,EAAY,OAAEC,EAAM,YAAEC,EAAW,KAAE3oD,EAAI,SAAEC,GAAa8nD,EACjF,OACE,UAAChyC,MAAAA,CAAI3a,UAAU,S,WACb,SAAC2a,MAAAA,CAAI3a,UAAU,gD,SACA,MAAZotD,EAAmB,GAAGZ,GAAOY,SAAkB,gBAElC,MAAfG,IACC,SAAC5yC,MAAAA,CAAI3a,UAAU,+BAA+Bgc,cAAY,qB,UACvDrX,EAAAA,GAAAA,IAAcC,EAAMC,KAKL,QAAnBm7C,GAA4BmN,GAAuB,MAAZC,IACtC,UAACzyC,MAAAA,CAAI3a,UAAU,iE,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,6B,WACb,SAAC+b,OAAAA,C,SACqB,aAAnBikC,EAAgC,iBAAmBp3B,GAAiBo3B,MAEvE,SAACjkC,OAAAA,CAAK/b,UAAU,Q,SAASwsD,GAAOa,SAElC,UAAC1yC,MAAAA,CAAI3a,UAAU,6B,WAMb,SAAC+b,OAAAA,C,SACqB,QAAnBikC,EACG,oBACmB,aAAnBA,EACE,8BACA,gBAAgBp3B,GAAiBo3B,QAEzC,SAACjkC,OAAAA,CACC/b,UACqB,QAAnBggD,GACmB,aAAnBA,GACU,MAAVsN,GACAA,EAAS,EACL,qCACA,Q,SAGLd,GAAOc,EAAQ,CAAE1sD,UAA8B,QAAnBo/C,cAO3C,CC3DO,SAASwN,GAAgBC,GAC9B,OAAU,MAANA,EAAmB,IACnBA,GAAM,IAAe,IAAGhtD,EAAAA,EAAAA,GAAMgtD,EAAK,IAAQ,QACxC,IAAGhtD,EAAAA,EAAAA,GAAMgtD,OAClB,CAGO,SAASC,GAAapqD,GAC3B,OAAS,MAALA,EAAkB,IACf,IAAG7C,EAAAA,EAAAA,GAAM6C,EAAG,MACrB,CAiCO,MCnGDqqD,GAAe,UAMN,SAASC,GAAW,G,IAAA,OAAElI,GAAF,EACjC,MAAM,MAAEtO,EAAK,SAAEmK,GAAaE,MACtB,QAAEhB,EAASpoC,KAAMw1C,EAAe,UAAE7gC,GAAcoqB,EAAMsJ,OAMtDoN,GAAUrtB,EAAAA,EAAAA,QAAa,KACtBpoB,EAAM01C,IAAgB/vD,EAAAA,EAAAA,UAAe,KACrCytD,EAAQuC,IAAahwD,EAAAA,EAAAA,UAAkC,MAExDiwD,GAAUp4C,EAAAA,EAAAA,aAAaiP,IAC3BgpC,EAAQlqB,QAAU9e,EAClBipC,EAAajpC,IACZ,KAGH7mB,EAAAA,EAAAA,WAAU,KACJwiD,IACFwN,EAAQ,IACRD,EAAU,QAEX,CAACvN,EAASwN,IAEb,MAAMC,GAAWr4C,EAAAA,EAAAA,aAAY,KAC3B,MAAMiP,EAASgpC,EAAQlqB,QACnB9e,EAAO3gB,OD7Ce,IC8C1B6pD,EAAU,MACVzM,EAAS,CAAEn6C,KAAM,kBAAmBiR,KAAMyM,MACzC,CAACy8B,KAGJtjD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,IAAQ61C,EAAS,OAEtB,MAAM0N,EAAW,CAAC1rD,EAAqBC,KACrC,MAAM0rD,EAAKxjD,EAAIyjD,QAAQ5rD,GACjB6rD,EAAK1jD,EAAIyjD,QAAQ3rD,GACvB,OAAO5B,KAAKgiC,MAAMsrB,EAAG91C,EAAIg2C,EAAGh2C,EAAG81C,EAAG71C,EAAI+1C,EAAG/1C,IA/CxB,IAkDbg2C,EAAUp+C,IACd,MAAM6U,EAAyB,CAAC7U,EAAEy7C,OAAOpsB,IAAKrvB,EAAEy7C,OAAO/iC,KACjD2lC,EAAUV,EAAQlqB,QAKpB4qB,EAAQrqD,QDpEY,GCoEgBgqD,EAASK,EAAQ,GAAIxpC,GAC3DkpC,IAIFD,EAAQ,IAAIO,EAASxpC,KAGjBypC,EAAgBt+C,IAEpBA,EAAEm/B,iBAIF,MAAMxqB,EAAS,IAAIgpC,EAAQlqB,SAC3B,KAAO9e,EAAO3gB,ODnFU,GCmFiBgqD,EAASrpC,EAAOA,EAAO3gB,OAAS,GAAI2gB,EAAOA,EAAO3gB,OAAS,KAClG2gB,EAAOlS,MAETq7C,EAAQnpC,GAERopC,KAGIQ,EAAcv+C,IAClB69C,EAAU,CAAC79C,EAAEy7C,OAAOpsB,IAAKrvB,EAAEy7C,OAAO/iC,OAMpC,OAHAje,EAAIkhD,GAAG,QAASyC,GAChB3jD,EAAIkhD,GAAG,WAAY2C,GACnB7jD,EAAIkhD,GAAG,YAAa4C,GACb,KACL9jD,EAAImhD,IAAI,QAASwC,GACjB3jD,EAAImhD,IAAI,WAAY0C,GACpB7jD,EAAImhD,IAAI,YAAa2C,KAEtB,CAAChJ,EAAQjF,EAASyN,EAAUD,KAG/BhwD,EAAAA,EAAAA,WAAU,KACR,IAAKwiD,EAAS,OAEd,MAAMkO,EAAWx+C,IACD,WAAVA,EAAEvI,IACJ25C,EAAS,CAAEn6C,KAAM,kBACE,UAAV+I,EAAEvI,IACXsmD,IACmB,cAAV/9C,EAAEvI,MACXuI,EAAEm/B,iBACF2e,EAAQH,EAAQlqB,QAAQvhC,MAAM,GAAI,MAKtC,OADAuoB,OAAOpY,iBAAiB,UAAWm8C,GAC5B,IAAM/jC,OAAOnX,oBAAoB,UAAWk7C,IAClD,CAAClO,EAASyN,EAAUD,EAAS1M,KAGhCtjD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,GAAKhgD,GAAQ61C,EAEb,OADA71C,EAAI4gD,YAAY/gD,MAAMghD,OAAS,YACxB,KACL7gD,EAAI4gD,YAAY/gD,MAAMghD,OAAS,KAEhC,CAAChL,EAASiF,KAKbznD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OAEV,MAAMgkD,EAAY,KAChB,IAAK,MAAMzrD,IAAM,CACf,cACA,gBACA,oBACA,wBACA,wBACA,0BAEIyH,EAAIqhD,SAAS9oD,IAAKyH,EAAIshD,UAAU/oD,IAMxC,OAFAyH,EAAIkhD,GAAG,OAAQ8C,GACfA,IACO,KACLhkD,EAAImhD,IAAI,OAAQ6C,KAEjB,CAAClJ,EAAQjF,EAASoN,EAAiB7gC,IAEtC,MAAMkS,GAAUr3B,EAAAA,EAAAA,SACd,IDzJG,SAAuBwQ,EAAYozC,GACxC,MAAM3mC,EAAS2mC,EAAS,IAAIpzC,EAAMozC,GAAUpzC,EACtC8mB,EAA8B,GAEhCra,EAAO3gB,QAbiB,EAc1Bg7B,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAY,CAAC,EACbjT,SAAU,CAAEj4B,KAAM,UAAWsQ,YAAa,CAAC,IAAIoN,EAAQA,EAAO,QAEvDA,EAAO3gB,QAAU,GAC1Bg7B,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAY,CAAC,EACbjT,SAAU,CAAEj4B,KAAM,aAAcsQ,YAAaoN,KAMjD,IAAK,MAAO8U,EAAO5U,KAAS3M,EAAKrO,UAC/Bm1B,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAY,CAAErF,OAAkB,IAAVrT,GACtByF,SAAU,CAAEj4B,KAAM,QAASsQ,YAAasN,KAI5C,MAAO,CAAE5d,KAAM,oBAAqB+3B,WACtC,CC4HU0vB,CAAcpO,EAAUpoC,EAAQw1C,GAAmB,GAAKpN,EAAUgL,EAAS,MACjF,CAAChL,EAASpoC,EAAMw1C,EAAiBpC,IAG7BqD,GAAgBjnD,EAAAA,EAAAA,SACpB,IDzHG,SACLmd,EACA9H,GAEA,IAAK8H,GAAQA,EAAK7gB,OAAS,EAAG,MAAO,CAAEiD,KAAM,oBAAqB+3B,SAAU,IAC5E,MAAOK,EAAK3W,GAAOkW,GAAU/Z,EAAK,GAAIA,EAAK,IAC3C,MAAO,CACL5d,KAAM,oBACN+3B,SAAU,CACR,CACE/3B,KAAM,UACNkrC,WAAY,CAAEp1B,SACdmiB,SAAU,CAAEj4B,KAAM,QAASsQ,YAAa,CAAC8nB,EAAK3W,MAItD,CC0GMkmC,CACE/hC,GAAWgiC,iBAIX,aAAatB,GAAa1gC,GAAWiiC,WAAWjhD,QAAQ,IAAK,QAEjE,CAACgf,IAGH,OAAKyzB,GAAYoN,GAGf,sB,WACE,UAACxB,GAAAA,GAAMA,CAAClpD,GAAG,SAASiE,KAAK,UAAU0I,KAAMovB,E,WACvC,SAACotB,GAAAA,GAAKA,CACJnpD,GAAG,cACHiE,KAAK,OACLnF,OAAQ,CAAC,KAAM,QAAS,WACxBsqD,MAAO,CAAE,aAAcoB,GAAc,eAAgB,QAEvD,SAACrB,GAAAA,GAAKA,CACJnpD,GAAG,gBACHiE,KAAK,OACLnF,OAAQ,CAAC,KAAM,QAAS,SACxBsqD,MAAO,CACL,aAAcoB,GACd,aAAc,IACd,iBAAkBlN,EAAU,CAAC,EAAG,GAAK,CAAC,EAAG,OAG7C,SAAC6L,GAAAA,GAAKA,CACJnpD,GAAG,oBACHiE,KAAK,SACLnF,OAAQ,CAAC,KAAM,QAAS,SACxBsqD,MAAO,CAEL,gBAAiB,CAAC,OAAQ,CAAC,MAAO,UAAW,EAAG,GAChD,eAAgB,UAChB,sBAAuBoB,GACvB,sBAAuB,QAK5BmB,EAAc3vB,SAASh7B,OAAS,IAC/B,UAACkoD,GAAAA,GAAMA,CAAClpD,GAAG,mBAAmBiE,KAAK,UAAU0I,KAAMg/C,E,WACjD,SAACxC,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACLmlD,MAAO,CAAE,gBAAiB,GAAI,eAAgB,UAAW,iBAAkB,QAE7E,SAACD,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACLmlD,MAAO,CACL,gBAAiB,IACjB,eAAgB,UAChB,sBAAuB,UACvB,sBAAuB,QAG3B,SAACD,GAAAA,GAAKA,CACJnpD,GAAG,yBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,SACtB,YAAa,GACb,cAAe,CAAC,EAAG,KACnB,cAAe,MACf,sBAAsB,GAExB3C,MAAO,CACL,aAAc,UACd,kBAAmB,UACnB,kBAAmB,aAjEU,IAwE3C,CClOe,SAAS4C,GAAc,G,IAAA,QACpCrxD,EAAO,KACP+E,EAAI,QACJ3D,EAAO,IACPkwD,EAAG,UACHC,EAAS,SACTC,EAAQ,aACRC,GAAe,GAPqB,EASpC,MAAM/rD,EAASX,GAAOnB,EAAAA,GAAAA,IAAemB,EAAM3D,GAAW,MAC/CswD,EAAWC,IAAgBzxD,EAAAA,EAAAA,UAAwBwF,IAEnDT,EAAS2sD,IAAc1xD,EAAAA,EAAAA,UAAmB,IAC3C2xD,GAAU/sD,EAAAA,GAAAA,IAAW9E,EAAS+E,EAAM2sD,EAAWtwD,EAAS6D,GACxD6sD,GAASnvB,EAAAA,EAAAA,QAA+D,MACxEovB,EAAe/xD,EAAQqG,OAAS,GAGtClG,EAAAA,EAAAA,WAAU,KACR,IAAKuF,EAEH,YADIqsD,GAAchjC,GAAI,kBAGxB,MAAM3C,EAAOiC,GAAY,kBAAmB,CAAEvnB,KAAMpB,IAEpD,OADAosD,EAAOhsB,QAAU,CAAEzgC,GAAIK,EAAQ0mB,QACxB,KACD0lC,EAAOhsB,SAASzgC,KAAOK,IACzBosD,EAAOhsB,QAAU,KACjB1Z,EAAK,iBAGR,CAAC1mB,EAAQqsD,KAEZ5xD,EAAAA,EAAAA,WAAU,KACRwxD,EAAcprD,IAAWD,EAAAA,GAAAA,IAAkBC,EAAQb,EAAQ6rD,IAC3D,MAAMS,EAAIF,EAAOhsB,QACbksB,GAAKT,EAAUS,EAAE3sD,MACnBysD,EAAOhsB,QAAU,KACjBksB,EAAE5lC,SAEH,CAAC1mB,EAAQ4rD,EAAKC,IAEjB,MAAMU,EAAaJ,EAAQ/kD,IAAKtH,GAAMA,EAAEH,IAAIiC,KAAK,KAWjD,OAVAnH,EAAAA,EAAAA,WAAU,KACR,IAAKsxD,EAAc,OACnB,MAAMS,EAAMD,EAAWtrD,MAAM,KAAKxC,OAAO8nB,SACzC,IAAKimC,EAAIC,MAAO9sD,GAAOksD,EAAUlsD,IAAM,OACvC,MAAM+sD,GAAQnsD,EAAAA,GAAAA,IAAgBjG,EAAS+E,EAAM3D,EAAS,IAAI2E,IAAImsD,GAjD1C,GAmDhBE,EAAM/rD,QAAQurD,EAAYvtD,GAAM,IAAI,IAAI0B,IAAI,IAAI1B,KAAM6tD,KAAQE,OAEjE,CAACd,EAAKG,KAGP,qB,SACGI,EAAQ/kD,IAAKtH,IACZ,SAAC+oD,GAAAA,GAAMA,CAAYlpD,GAAIG,EAAEH,GAAIiE,KAAK,SAAShE,IAAK,cAAcE,EAAEF,MAAOmE,SAAU,I,UAC/E,SAAC+kD,GAAAA,GAAKA,CACJnpD,GAAI,GAAGG,EAAEH,UACTiE,KAAK,SACLkoD,SAAUA,EACV/C,MAAO,CACL,iBAAkBjpD,EAAEH,KAAOqsD,EAAY,EAAI,EAC3C,4BAA6B,CAAE/gC,SAAU,EAAG0hC,MAAO,GACnD,uBAAwB,MARjB7sD,EAAEH,MAevB,CCvEA,SAASu8B,GAAON,GACd,OAAO,EAASkT,YAAYA,YAAsD,CAAC,CACrF,CAEA,SAAS8d,GAAY1vD,GACnB,MAAyB,iBAAXA,GAAuBgE,OAAOi4B,SAASj8B,GAAUA,EAAS,IAC1E,CAEA,SAAS2vD,GAAY3vD,GACnB,MAAyB,iBAAXA,GAAyC,KAAlBA,EAAOsE,OAAgBtE,EAAS,IACvE,CAMA,SAAS4vD,GAAclxB,GACrB,MAAMqG,EAAQ4qB,GAAY3wB,GAAON,GAAS6S,WAC1C,GAAIxM,EAAO,OAAOA,EAClB,MAAM9oB,EAAO0zC,GAAYjxB,EAAQkT,YAAY31B,MAC7C,OAAOA,EAAOA,EAAK3O,QAAQ,UAAW,IAAM,IAC9C,CAEA,SAASuiD,GAAc3uD,EAAyBw9B,GAC9C,MAAMvnB,EAAI6nB,GAAON,GACXziB,EAAO0zC,GAAYjxB,EAAQkT,YAAY31B,OAAS,GACtD,MAAO,CACL/a,QACA+a,OACA4J,KAAM8pC,GAAYjxB,EAAQkT,YAAY9nC,OAASmS,EAC/C4H,IAAe,iBAAV3iB,EAA2B,KAAO0uD,GAAclxB,GACrDoxB,QACY,iBAAV5uD,EACI,KACA,CAAE6mB,IAAK2nC,GAAYv4C,EAAE4Q,KAAMC,IAAK0nC,GAAYv4C,EAAE6Q,KAAMC,IAAKynC,GAAYv4C,EAAE8Q,MAC7E8nC,YAAuB,iBAAV7uD,EAA2BwuD,GAAYv4C,EAAE64C,SAAW,KACjEC,aAAcN,GAAYx4C,EAAE44B,aAEhC,CAEA,SAASxD,GACPzN,EACA3W,EACA+nC,GAEA,OAAOA,GAAWzxB,SAAS/8B,KAAMiwC,GAAMA,EAAEhT,UAAYkkB,GAAe/jB,EAAK3W,EAAKwpB,KAAO,IACvF,CAOO,SAASwe,GACdrxB,EACA3W,EACArjB,GAEA,MAAM+e,EAAM0oB,GAAOzN,EAAK3W,EAAKrjB,EAAQ65C,cAC/B1iC,EAAO0zC,GAAY9rC,GAAK+tB,YAAY31B,MAC1C,GAAI4H,GAAO5H,EACT,MAAO,CACL/a,MAAO,MACP+a,OACA4J,KAAM5J,EACN4H,IAAK5H,EACL6zC,QAAS,CACP/nC,IAAK2nC,GAAY7rC,EAAI+tB,YAAYQ,WACjCpqB,IAAK0nC,GAAY7rC,EAAI+tB,YAAYgS,WACjC37B,IAAKynC,GAAY7rC,EAAI+tB,YAAYS,YAEnC0d,YAAa,KACbE,aAAc,MAGlB,MAAMG,EAA+E,CACnF,CAAC,aAActrD,EAAQ+5C,aACvB,CAAC,QAAS/5C,EAAQi6C,QAClB,CAAC,gBAAiBj6C,EAAQg6C,gBAC1B,CAAC,eAAgBh6C,EAAQk6C,gBAE3B,IAAK,MAAO99C,EAAOgvD,KAAcE,EAAU,CACzC,MAAM1xB,EAAU6N,GAAOzN,EAAK3W,EAAK+nC,GACjC,GAAIxxB,EAAS,OAAOmxB,GAAc3uD,EAAOw9B,EAC3C,CACA,OAAO,IACT,CAGO,SAAS2xB,GAASxkD,GACvB,OAAQA,EAAO3K,OACb,IAAK,MACH,OAAO2K,EAAOoQ,KAChB,IAAK,aACH,MAAO,kBAAkBpQ,EAAOgY,MAClC,IAAK,QACH,MAAO,SAAShY,EAAOga,OACzB,IAAK,gBACH,MAAO,iBAAiBha,EAAOga,OACjC,IAAK,eACH,OAAOha,EAAOga,KAAKyqC,cAAcrjD,WAAW,gBACxC,gBAAgBpB,EAAOoQ,OACvB,gBAAgBpQ,EAAOga,OAEjC,CCjGO,MAAM0qC,GAA6B,CACxC9D,QAAS,KACTF,KAAM,KACNG,SAAU,KACVC,aAAc,KACdC,OAAQ,KACRC,YAAa,KACb3oD,KAAM,KACNC,UAAU,GCfL,SAAS2nC,GAAU9rC,EAAgBe,GACxC,MAAMZ,EAAQC,KAAKC,IAAIL,GAAQM,eAAe,QAAS,CACrDC,sBAAuB,EACvBC,sBAAuB,IAKzB,MAAmB,QAAfO,GAAuC,QAAfA,EACnBf,EAAS,EAAI,IAAIG,IAAUA,EAGtB,QAAVA,EAAwB,MACrBH,EAAS,EAAI,IAAIG,IAAU,IAAIA,GACxC,CC1BA,MAAM85B,GAAQ,IAAI7zB,IACZg7C,GAAW,IAAIh7C,IAoDrB,MAAMoqD,GAAkC,CAAE9pD,KAAM,oBAAqB+3B,SAAU,IAExE,SAASgyB,GAAiB,G,IAAA,SAC/BvwC,EAAQ,WACRnf,EAAU,QACV5D,GAH+B,EAS/B,MAAOuzD,EAAYC,IAAiBrzD,EAAAA,EAAAA,UAAqB,KAClDi2C,EAAOqd,IAAYtzD,EAAAA,EAAAA,WAAS,GAG7BstB,GAAYmV,EAAAA,EAAAA,QAAO,GAEnBuvB,GAAMnoD,EAAAA,EAAAA,SAAQ,IAAM+Y,EAAShW,IAAKyS,GAAOA,EAAGla,IAAIiC,KAAK,KAAM,CAACwb,KAElE3iB,EAAAA,EAAAA,WAAU,KACR,IAAKJ,GAA+B,IAApB+iB,EAASzc,OAGvB,OAFAktD,EAAc,SACdC,GAAS,GAIX,MAAMC,IAASjmC,EAAUsY,QACzB0tB,GAAS,GAETrgD,QAAQ2hB,IAAIhS,EAAShW,IAAKyS,GA3E9Bjf,eACEw1B,EACAnyB,GAEA,MAAMmG,EARR,SAAkBgsB,EAAmBnyB,GACnC,MAAO,GAAGmyB,KAAanyB,GACzB,CAMcu7B,CAASpJ,EAAWnyB,GAC1BswB,EAAS4I,GAAMlyB,IAAIb,GACzB,GAAImqB,EAAQ,OAAOA,EACnB,MAAM9N,EAAQ69B,GAASr5C,IAAIb,GAC3B,GAAIqc,EAAO,OAAOA,EAElB,MAAMwO,GlEwR+E/mB,EkExRpC,CAC/C8B,KAAM,CAAErK,GAAIywB,GACZ5mB,MAAO,CAAEvL,gBlEuRDiK,EAAQoD,QAAUA,GAAQrG,IAAsE,CACpGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,uCACFsI,KkE/RNhN,KAAM,I,IAAA,KAAEoR,GAAM,EACb,MAAM0U,EAAW1U,EACjB,IAAK0U,EAAU,OAAO,KACtB,MAAMgtC,EAAqB,CACzB1sC,OAAQN,EAASM,QAAU,GAC3B2sC,mBAAoBjtC,EAASktC,qBAC7BC,SAAUntC,EAASmtC,UAGrB,OADAh3B,GAAMlxB,IAAI7B,EAAK4pD,GACRA,IAER/sC,MAAM,IAAM,MACZC,QAAQ,IAAMo9B,GAAS9tC,OAAOpM,IlEwQa,IAAuC8D,EkErQrF,OADAo2C,GAASr4C,IAAI7B,EAAK6qB,GACXA,CACT,CA6CqCm/B,CAAYv0C,EAAGla,GAAI1B,KAAc/C,KAAMmzD,IAClEvmC,EAAUsY,UAAY2tB,IAC1BF,EAAcQ,EAAW5vD,OAAQ5D,GAA2B,OAANA,IACtDizD,GAAS,OAMV,CAACtB,EAAKvuD,EAAY5D,IAErB,MAAMqhC,GAAUr3B,EAAAA,EAAAA,SAAQ,IACjBhK,GAAiC,IAAtBuzD,EAAWjtD,ODlExB,SACL2gB,EACArjB,GAEA,MAAM09B,EAA8B,GAEpC,IAAK,MAAMna,KAAQF,EAAQ,CACzB,GAAIE,EAAK7gB,OAAS,EAAG,SACrB,MAAOmU,EAAGC,EAAG7X,GAAUskB,EACvB,IAAKtgB,OAAOi4B,SAASj8B,GAAS,SAE9B,MAAO8+B,EAAK3W,GAAOkW,GAAUzmB,EAAGC,GAC1BrY,EAA2B,CAC/BQ,SACAwc,MAAOsvB,GAAU9rC,EAAQe,GAEzB4a,SAAU3b,EACVoxD,MAAsB,QAAfrwD,GAAuC,QAAfA,GAAwBf,EAAS,GAGlEy+B,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAYpyC,EACZm/B,SAAU,CAAEj4B,KAAM,QAASsQ,YAAa,CAAC8nB,EAAK3W,KAElD,CAEA,MAAO,CAAEzhB,KAAM,oBAAqB+3B,WACtC,CCwCW4yB,CADQX,EAAWtM,QAASnvC,GAAMA,EAAEmP,QACPrjB,GAFYyvD,GAG/C,CAACE,EAAY3vD,EAAY5D,IAE5B,MAAO,CACLqhC,UACA+U,QACA+d,uBACiB,QAAfvwD,EAAuB,EAAI2vD,EAAWnvD,OAAQ0T,IAAOA,EAAE87C,oBAAoBttD,OAC7EwtD,SAAUP,EAAWnvD,OAAQ0T,GAAMA,EAAEg8C,UAAUxtD,OAEnD,CCnFO,SAAS8tD,GAAexwD,GAC7B,MAAsB,QAAfA,GAAuC,QAAfA,CACjC,CAUO,SAASywD,GAAiBC,GAC/B,MAAMC,EAAOtxD,KAAKC,IAAIoxD,EAAS,GAAK,KAC9BtxD,EAAQC,KAAKC,IAAIoxD,GAAQnxD,eAAe,QAAS,CACrDC,sBAAuBmxD,EAAO,EAAI,EAClClxD,sBAAuBkxD,EAAO,EAAI,IAEpC,OAAOD,EAAS,EAAI,IAAItxD,IAAUA,CACpC,CA0CO,MAAMwxD,GAAgB,wBCzFvB13B,GAAQ,IAAI7zB,IACZg7C,GAAW,IAAIh7C,IAkDrB,MAAMoqD,GAAkC,CAAE9pD,KAAM,oBAAqB+3B,SAAU,IAExE,SAASmzB,GAAmB,G,IAAA,SACjC1xC,EAAQ,WACRnf,EAAU,SACV8wD,EAAQ,QACR10D,GAJiC,EAWjC,MAAOuzD,EAAYC,IAAiBrzD,EAAAA,EAAAA,UAAqB,KAClDi2C,EAAOqd,IAAYtzD,EAAAA,EAAAA,WAAS,GAE7BstB,GAAYmV,EAAAA,EAAAA,QAAO,GAEnB+xB,EAAS30D,GAAWo0D,GAAexwD,GACnCuuD,GAAMnoD,EAAAA,EAAAA,SAAQ,IAAM+Y,EAAShW,IAAKyS,GAAOA,EAAGla,IAAIiC,KAAK,KAAM,CAACwb,KAElE3iB,EAAAA,EAAAA,WAAU,KACR,IAAKu0D,GAA8B,IAApB5xC,EAASzc,OAGtB,OAFAktD,EAAc,SACdC,GAAS,GAIX,MAAMC,IAASjmC,EAAUsY,QACzB0tB,GAAS,GAETrgD,QAAQ2hB,IAAIhS,EAAShW,IAAKyS,GA3E9Bjf,eACEw1B,EACAnyB,EACA8wD,GAEA,MAAM3qD,EATR,SAAkBgsB,EAAmBnyB,EAA4B8wD,GAC/D,MAAO,GAAG3+B,KAAanyB,KAAc8wD,GACvC,CAOcv1B,CAASpJ,EAAWnyB,EAAY8wD,GACtCxgC,EAAS4I,GAAMlyB,IAAIb,GACzB,GAAImqB,EAAQ,OAAOA,EACnB,MAAM9N,EAAQ69B,GAASr5C,IAAIb,GAC3B,GAAIqc,EAAO,OAAOA,EAElB,MAAMwO,GpEkTiF/mB,EoElTpC,CACjD8B,KAAM,CAAErK,GAAIywB,GACZ5mB,MAAO,CAAEvL,aAAY8wD,cpEiTb7mD,EAAQoD,QAAUA,GAAQrG,IAAwE,CACtGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,yCACFsI,KoEzTNhN,KAAM,I,IAAA,KAAEoR,GAAM,EACb,MAAM0U,EAAW1U,EACjB,IAAK0U,EAAU,OAAO,KACtB,MAAMgtC,EAAqB,CACzBiB,OAAQjuC,EAASiuC,QAAU,GAC3Bd,SAAUntC,EAASmtC,UAGrB,OADAh3B,GAAMlxB,IAAI7B,EAAK4pD,GACRA,IAER/sC,MAAM,IAAM,MACZC,QAAQ,IAAMo9B,GAAS9tC,OAAOpM,IpEmSe,IAAuC8D,EoEhSvF,OADAo2C,GAASr4C,IAAI7B,EAAK6qB,GACXA,CACT,CA6CqCigC,CAAcr1C,EAAGla,GAAI1B,EAAY8wD,KAAY7zD,KAC3EmzD,IACKvmC,EAAUsY,UAAY2tB,IAC1BF,EAAcQ,EAAW5vD,OAAQ5D,GAA2B,OAANA,IACtDizD,GAAS,OAKZ,CAACtB,EAAKvuD,EAAY8wD,EAAUC,IAE/B,MAAMtzB,GAAUr3B,EAAAA,EAAAA,SAAQ,IACjB2qD,GAAgC,IAAtBpB,EAAWjtD,OD7CvB,SACLsuD,GAEA,MAAMtzB,EAA8B,GAEpC,IAAK,MAAM4J,KAAQ0pB,EAAQ,CACzB,IAAK/tD,OAAOi4B,SAASoM,EAAKopB,SAAWppB,EAAKjkB,OAAO3gB,OAAS,EAAG,SAE7D,MAAMuT,EAAkC,GACxC,IAAK,MAAMsN,KAAQ+jB,EAAKjkB,OAClBE,EAAK7gB,OAAS,GAClBuT,EAAYnU,KAAKw7B,GAAU/Z,EAAK,GAAIA,EAAK,KAE3C,GAAItN,EAAYvT,OAAS,EAAG,SAE5B,MAAMiuD,EAAOtxD,KAAKC,IAAIgoC,EAAKopB,OAAS,GAAK,KACnCjyD,EAA4B,CAChCiyD,OAAQppB,EAAKopB,OACbj1C,MAAOg1C,GAAiBnpB,EAAKopB,QAC7BC,OAEA/1C,SAAU+1C,EAAO,EAAI,KAAQrpB,EAAKopB,QAGpChzB,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAYpyC,EACZm/B,SAAU,CAAEj4B,KAAM,aAAcsQ,gBAEpC,CAEA,MAAO,CAAEtQ,KAAM,oBAAqB+3B,WACtC,CCcWwzB,CAAuBvB,EAAWtM,QAASnvC,GAAMA,EAAE88C,SADXvB,GAE9C,CAACE,EAAYoB,IAEhB,MAAO,CACLtzB,UACA+U,QACA0d,SAAUP,EAAWnvD,OAAQ0T,GAAMA,EAAEg8C,UAAUxtD,OAEnD,CCxHO,SAASyuD,GAAiBt6C,EAAWC,GAC1C,MAAMinB,EAAM,EAAK,YAAe,IAChC,IAAI3W,EAAM,EAAK,YAAe,IAE9B,OADAA,EAAM,IAAO/nB,KAAKqX,IAAO,EAAIrX,KAAK+xD,KAAK/xD,KAAKgyD,IAAI,EAAOhyD,KAAKqX,GAAM,MAAQrX,KAAKqX,GAAK,GAC7E,CAACqnB,EAAK3W,EACf,CCgEO,SAASkqC,GACdnyC,EACAoyC,G,IACAC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GA3CyB,IA6CzB,OAAKD,EAEEpyC,EAAS3e,OAAQob,IACtB,MAAM,UAAE61C,EAAS,UAAEC,EAAS,UAAEC,EAAS,UAAEC,GAAch2C,EAGvD,OAAiB,MAAb61C,GAAkC,MAAbC,GAAkC,MAAbC,GAAkC,MAAbC,GAhChE,SACLC,EACAC,EACAC,EACAC,EACAT,GAGA,IAAKA,EAAQ,OAAO,EACpB,MAAM51B,EA1BR,SAAkB41B,EAAqBC,GACrC,MAAMz6C,GAAMw6C,EAAO/sD,KAAO+sD,EAAO9sD,MAAQ+sD,EACnCx6C,GAAMu6C,EAAOjtD,MAAQitD,EAAOhtD,OAASitD,EAC3C,MAAO,CACL/sD,KAAM8sD,EAAO9sD,KAAOsS,EACpBvS,KAAM+sD,EAAO/sD,KAAOuS,EACpBxS,MAAOgtD,EAAOhtD,MAAQyS,EACtB1S,MAAOitD,EAAOjtD,MAAQ0S,EAE1B,CAiBgBi7C,CAASV,EAHvBC,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAzByB,MA6BlB/sD,EAAMF,GAAS4sD,GAAiBU,EAAMC,IACtCttD,EAAMF,GAAS6sD,GAAiBY,EAAMC,GAC7C,OAAOxtD,GAAQm3B,EAAMl3B,MAAQA,GAAQk3B,EAAMn3B,MAAQF,GAASq3B,EAAMp3B,OAASA,GAASo3B,EAAMr3B,KAC5F,CAsBW4tD,CAAYT,EAAWC,EAAWC,EAAWC,EAAWL,EAAQC,KATrD,IAAIryC,EAW1B,CCnFO,SAASgzC,GAAgBC,EAAkBrF,GAChD,MAAMsF,EAAOD,EAAOE,QAAQvF,GAC5B,GAAIsF,EAAO,EAAG,MAAO,GACrB,MAAM7wD,EAAgB,GACtB,IAAK,IAAI6gB,EAAO,EAAGA,EAAO+vC,EAAO1vD,OAAQ2f,IAAQ,CAC/C,MAAMkwC,EAAKF,EAAOhwC,EACZmwC,EAAOH,EAAOhwC,EAChBkwC,EAAKH,EAAO1vD,QAAQlB,EAAIM,KAAKswD,EAAOG,IACpCC,GAAQ,GAAGhxD,EAAIM,KAAKswD,EAAOI,GACjC,CACA,OAAOhxD,CACT,CCDA,MAGMixD,GAAmE,IAAlDzpC,GAAgB,qBAAsB,CAAC,GAAI,GAY3D,SAAS0pC,GAAYhxD,EAAYixD,GACtC,MAAO,GAAGjxD,KAAMixD,GAClB,CA8CO,SAASC,GAAaR,EAAkBrF,G,IAAiBx2C,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAM67C,EAAO1vD,OAC3E,OAAK0vD,EAAO5mD,SAASuhD,GACd,CAACA,KAAYoF,GAAgBC,EAAQrF,IAAUnsD,MAAM,EAAG2V,GADzB,EAExC,CAuFO5Z,eAAek2D,GACpBj3C,EACA2iC,GAEA,MAAuB,QAAnBA,GAA+C,QAAnBA,EAAiC,KAC1C,aAAnBA,EAC6B,MAAxB3iC,EAAG2T,kBAA4B+C,GAAiB1W,EAAG2T,mBAAqB,KAE1E+wB,GAAmB1kC,EAAG4D,eAAgB++B,EAC/C,CA+BA,SAASuU,GACPC,EACAxU,EACApuB,EACAwL,GAEA,MAAMq3B,EAAW,IAAI5wD,IAAI2wD,EAAUxqD,QAAQY,IAAKyjB,GAAUA,EAAMlrB,KAChE,OACEgI,KAAKoU,MAAQi1C,EAAUE,WAAa,MACpCF,EAAUJ,WAAapU,GACvBwU,EAAUG,YAAc/iC,GAGxBmhC,GAAgByB,EAAUI,UAAWx3B,GAClC/6B,MAAM,EAzMe,IA0MrB4tD,MAAO5hC,GAAUomC,EAAS3wD,IAAIuqB,EAAMlrB,IAE3C,C,cCvNO,SAAS0xD,GACdhB,EACAiB,GAEA,IAAKA,EAAc,OAAO,KAC1B,MAAMl7B,EAAQi6B,EAAOE,QAAQe,GAC7B,GAAIl7B,EAAQ,EAAG,OAAO,KACtB,GAAIA,EAAQ,EAAG,MAAO,CAAEoM,IAAK6tB,EAAOj6B,EAAQ,GAAIqM,IAAK6uB,GAErD,MAAMC,EAAQ,IAAI5pD,KAAK,GAAG2pD,eAE1B,OADAC,EAAMC,WAAWD,EAAM3jC,aAjBF,IAkBd,CAAE4U,IAAK+uB,EAAM3pD,cAAc/I,MAAM,EAAG,IAAK4jC,IAAK6uB,EACvD,CAQA,SAASjzD,GAAM2C,GACb,OAAO,IAAI2G,KAAK3G,GAAKsX,mBAAmB,QAAS,CAC/C6H,IAAK,UACLH,MAAO,OACPC,KAAM,WAEV,CA8CA,MAeMkX,GAAQ,IAAI7zB,IACZq2B,GAAS,IAAIr2B,IAEnB,SAAS43B,GAAQsH,EAAaC,GAC5B,MAAO,GAAGD,KAAOC,GACnB,CAeA,SAASgvB,GAAYjvB,EAAaC,GAChC,MAAMtnC,EAAI+/B,GAAQsH,EAAKC,GACjBivB,EAAUv6B,GAAMlyB,IAAI9J,GAC1B,GAAIu2D,EAAS,OAAOjkD,QAAQC,QAAQgkD,GACpC,MAAM7pC,EAAW8R,GAAO10B,IAAI9J,GAC5B,GAAI0sB,EAAU,OAAOA,EAErB,MAAM8pC,GzEzGmEzpD,EyEyGpC,CAAEsB,MAAO,CAAEg5B,MAAKC,SzExG3Cv6B,GAASoD,QAAUA,GAAQrG,IAA0D,CACzFuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,sBACFsI,KyE+FNhN,KAAM,I,IAAA,KAAEoR,GAAM,EACb,MAAMqgC,EAAKrgC,EAGL7L,EAASksC,GAAIhR,UAAUh7B,QAAU,EACjCixD,EAA4B,CAGhCl2B,QAASj7B,EAASg7B,GAAiBkR,GAAmC,KACtErK,QAASqK,GAAIrK,SAAW,KACxB7hC,UAGF,OAlCN,SAAiBtF,EAAWy2D,GAK1B,IAFAz6B,GAAM3mB,OAAOrV,GACbg8B,GAAMlxB,IAAI9K,EAAGy2D,GACNz6B,GAAMl7B,KA3BG,GA2Be,CAC7B,MAAMo7B,EAASF,GAAMpB,OAAOjN,OAAO5hB,MACnC,QAAekB,IAAXivB,EAAsB,MAC1BF,GAAM3mB,OAAO6mB,EACf,CACF,CAuBMw6B,CAAQ12D,EAAGy2D,GACJA,IAER1wC,QAAQ,KACPyY,GAAOnpB,OAAOrV,KzE1HgB,IAAuC+M,EyE8HzE,OADAyxB,GAAO1zB,IAAI9K,EAAGw2D,GACPA,CACT,CC7IA,MAAMhgC,GAAY,IAkBlB,SAASmgC,GAAMzsC,GACb,OAAO/nB,KAAKmtB,IAAIntB,KAAKkhC,IAAIlhC,KAAKqX,GAAK,EAAI,EAAOrX,KAAKqX,GAAM,KAC3D,CAGA,SAASo9C,GACP/1B,EACA3W,EACAmqC,EACAvzD,GAEA,MAAOyG,EAAMF,EAAOC,EAAMF,GAASitD,EAC7BlrC,GAAO0X,EAAMt5B,IAASD,EAAOC,GAASzG,EACtC+1D,EAASF,GAAMvvD,GACf0vD,EAASH,GAAMtvD,GAErB,MAAO,CAAC8hB,GADK0tC,EAASF,GAAMzsC,KAAS2sC,EAASC,GAAWh2D,EAE3D,CAQA,SAASi2D,GAAmBt2B,GAC1B,IAAIu2B,EAAS95B,IACX+5B,EAAS/5B,IACTg6B,GAAS,IACTC,GAAS,IAEX,MAAMC,EAASC,IACTA,EAAO,GAAKL,IAAQA,EAASK,EAAO,IACpCA,EAAO,GAAKJ,IAAQA,EAASI,EAAO,IACpCA,EAAO,GAAKH,IAAQA,EAASG,EAAO,IACpCA,EAAO,GAAKF,IAAQA,EAASE,EAAO,KAGpCv+C,EAAO2nB,EAAQC,SACrB,GAAkB,YAAd5nB,EAAKrQ,KACP,IAAK,MAAMiR,KAAQZ,EAAKC,YAAa,IAAK,MAAM6J,KAAKlJ,EAAM09C,EAAMx0C,QAC5D,GAAkB,iBAAd9J,EAAKrQ,KACd,IAAK,MAAMo8C,KAAQ/rC,EAAKC,YAAa,IAAK,MAAMW,KAAQmrC,EAAM,IAAK,MAAMjiC,KAAKlJ,EAAM09C,EAAMx0C,GAG5F,MAAO,CAACo0C,EAAQC,EAAQC,EAAQC,EAClC,CAEA,SAASG,GAAaxzD,EAAyBC,GAC7C,OAAOD,EAAE,IAAMC,EAAE,IAAMD,EAAE,IAAMC,EAAE,IAAMD,EAAE,IAAMC,EAAE,IAAMD,EAAE,IAAMC,EAAE,EACnE,CAMA,MAAMwzD,G,QACItrD,IAAM,IAAI9D,IAClB,WAAAkyB,CAAY,G,KAAQm9B,QAAAA,CAAkB,CAEtC1tD,GAAAA,CAAIb,GACF,MAAMzF,EAAIg3B,KAAKvuB,IAAInC,IAAIb,GAMvB,YALUgE,IAANzJ,IAEFg3B,KAAKvuB,IAAIoJ,OAAOpM,GAChBuxB,KAAKvuB,IAAInB,IAAI7B,EAAKzF,IAEbA,CACT,CAEAsH,GAAAA,CAAI7B,EAAa8C,GAGf,GAFIyuB,KAAKvuB,IAAI9G,IAAI8D,IAAMuxB,KAAKvuB,IAAIoJ,OAAOpM,GACvCuxB,KAAKvuB,IAAInB,IAAI7B,EAAK8C,GACdyuB,KAAKvuB,IAAInL,KAAO05B,KAAKg9B,QAAS,CAEhC,MAAMC,EAAQj9B,KAAKvuB,IAAI2uB,OAAOjN,OAAO5hB,WACvBkB,IAAVwqD,GAAqBj9B,KAAKvuB,IAAIoJ,OAAOoiD,EAC3C,CACF,EAYK,MAAMC,GACHl3B,SACAm3B,cACA37B,MAAQ,IAAIu7B,GAAqB,KAEzC,WAAAl9B,IAAe2uB,GACbxuB,KAAKgG,SAAWwoB,EAAY7C,QAASvjC,GAAMA,EAAE4d,UAC7ChG,KAAKm9B,cAAgBn9B,KAAKgG,SAASv0B,IAAI8qD,GACzC,CAGAa,OAAAA,CAAQzhC,EAAWxc,EAAWC,GAC5B,OAAO4gB,KAAKq9B,QAAQ1hC,EAAGxc,EAAGC,GAAGk+C,IAC/B,CAGAC,aAAAA,CAAc5hC,EAAWxc,EAAWC,GAClC,OAAO4gB,KAAKq9B,QAAQ1hC,EAAGxc,EAAGC,GAAGo+C,UAC/B,CAGAC,UAAAA,CAAWh9B,GACT,OAAOA,GAAS,GAAKA,GAAST,KAAKgG,SAASh7B,OAASg1B,KAAKgG,SAASvF,EAAQ,GAAK,IAClF,CAEQ48B,OAAAA,CAAQ1hC,EAAWxc,EAAWC,GACpC,MAAM3Q,EAAM,GAAGktB,KAAKxc,KAAKC,IACnBwZ,EAASoH,KAAKwB,MAAMlyB,IAAIb,GAC9B,GAAImqB,EAAQ,OAAOA,EAEnB,MAAM2K,EAAOvD,KAAK09B,UAAU/hC,EAAGxc,EAAGC,GAElC,OADA4gB,KAAKwB,MAAMlxB,IAAI7B,EAAK80B,GACbA,CACT,CAEQm6B,SAAAA,CAAU/hC,EAAWxc,EAAWC,GACtC,MAAMy6C,EAxIH,SAAoBl+B,EAAWxc,EAAWC,GAC/C,MAAM6b,EAAI,GAAKU,EACT5uB,EAAO,EAAKkuB,EAAK,IAAM,IACvBnuB,GAASqS,EAAI,GAAK8b,EAAK,IAAM,IAC7BruB,EAA6D,IAArD,KAAM8sD,KAAK/xD,KAAKg2D,KAAKh2D,KAAKqX,IAAM,EAAI,EAAKI,EAAK6b,KAActzB,KAAKqX,GAE/E,MAAO,CAACjS,EADiE,IAA3D,KAAM2sD,KAAK/xD,KAAKg2D,KAAKh2D,KAAKqX,IAAM,EAAI,GAAMI,EAAI,GAAM6b,KAActzB,KAAKqX,GAChElS,EAAMF,EAC7B,CAiImBgxD,CAAWjiC,EAAGxc,EAAGC,GAIhC,IADmB4gB,KAAKm9B,cAAcjzD,KAAM2zD,GAAOf,GAAajD,EAAQgE,IACvD,CACf,MAAMvhC,EAAS,IAAIC,gBAAgBP,GAAWA,IAC9C,MAAO,CACLshC,KAAM,IAAI//B,WAAWvB,OACrBwhC,WAAYlhC,EAEhB,CAEA,MAAMA,EAAS,IAAIC,gBAAgBP,GAAWA,IACxC5e,EAAMkf,EAAOG,WAAW,MAE9B,IAAK,IAAIqhC,EAAK,EAAGA,EAAK99B,KAAKgG,SAASh7B,OAAQ8yD,IAAM,CAChD,IAAKhB,GAAajD,EAAQ75B,KAAKm9B,cAAcW,IAAM,SAEnD,MACMx/C,EADU0hB,KAAKgG,SAAS83B,GACT53B,SACfzF,EAAQq9B,EAAK,EAKnB,GAFA1gD,EAAI2gD,UAAY,OAAOt9B,WAEL,YAAdniB,EAAKrQ,KACP+xB,KAAKg+B,YAAY5gD,EAAKkB,EAAKC,YAAas7C,QACnC,GAAkB,iBAAdv7C,EAAKrQ,KACd,IAAK,MAAMo8C,KAAQ/rC,EAAKC,YACtByhB,KAAKg+B,YAAY5gD,EAAKitC,EAAMwP,EAGlC,CAOA,MAAM30B,EAAY9nB,EAAI8f,aAAa,EAAG,EAAGlB,GAAWA,IAC9CshC,EAAO,IAAI//B,WAAWvB,OAC5B,IAAK,IAAI1xB,EAAI,EAAGA,EAAIgzD,EAAKtyD,OAAQV,IAAK,CACpC,MAAMpF,EAAIggC,EAAUvuB,KAAS,EAAJrM,GACnBhB,EAAI47B,EAAUvuB,KAAS,EAAJrM,EAAQ,GACjCgzD,EAAKhzD,GAAKhB,EAAI,IAAMpE,EAAI,CAC1B,CAEA,MAAO,CAAEo4D,OAAME,WAAYlhC,EAC7B,CAEQ0hC,WAAAA,CACN5gD,EACAiB,EACAw7C,GAEAz8C,EAAIytB,YACJ,IAAK,MAAM3rB,KAAQb,EAAO,CACxB,IAAK,IAAI/T,EAAI,EAAGA,EAAI4U,EAAKlU,OAAQV,IAAK,CACpC,MAAOqkB,EAAIC,GAAMwtC,GAAkBl9C,EAAK5U,GAAG,GAAI4U,EAAK5U,GAAG,GAAIuvD,EAAQ79B,IACzD,IAAN1xB,EAAS8S,EAAI0tB,OAAOnc,EAAIC,GACvBxR,EAAI2tB,OAAOpc,EAAIC,EACtB,CACAxR,EAAI4tB,WACN,CACA5tB,EAAIkD,MACN,EAOF,IAAI29C,GAAiC,KAE9B,SAASC,GAAYZ,GAC1BW,GAAWX,CACb,CAEO,SAASa,KACd,OAAOF,EACT,CAWOh5D,eAAem5D,GACpBxhC,EACA0gC,EACA3hC,EACAxc,EACAC,GAEA,MAAMo+C,EAAaF,EAAKC,cAAc5hC,EAAGxc,EAAGC,GAEtCkd,EAAS,IAAIC,gBAAgBP,GAAWA,IACxC5e,EAAMkf,EAAOG,WAAW,MAS9B,OANArf,EAAI2f,UAAUH,EAAQ,EAAG,GAGzBxf,EAAIihD,yBAA2B,iBAC/BjhD,EAAI2f,UAAUygC,EAAY,EAAG,GAEtB3gC,kBAAkBP,EAC3B,CC1OO,SAASgiC,GAAaC,EAAgBjL,GAC3C,MAAM,SAAE2H,EAAQ,SAAEt3B,EAAQ,KAAE66B,EAAI,IAAE/3C,GAAQ6sC,EAC1C,GAAIA,EAAOnlD,UAAYqwD,EAAM,MAAO,cAAclL,EAAOnlD,UACzD,GACEmjB,GAAgB,qBAAsB,CAAC,GAAI,IAC9B,QAAb2pC,GACa,aAAbt3B,IACC66B,IACA/3C,EACD,CACA,MAAMxc,EAAM,IAAI4e,IAAI01C,GACd7pD,EAAQzK,EAAIyrB,SAAShhB,MAAM,mCACjC,GAAIzK,EAAIwrB,SAAWhE,OAAOC,SAAS+D,QAAU/gB,EAC3C,MAAO,GAAGzK,EAAIwrB,mCAAmC/gB,EAAM,SAC3D,CAOA,MAAO,SAAS6pD,KAAU93C,EAAM,OAAS,QAAQw0C,KAFlCwD,EAAAA,GAAAA,IAAmBxD,GAAY,IAAIt3B,IAAa,KAEM66B,EAAO,QAAU,IACxF,C,eC1CA,SAASE,GACP7B,EACAD,GAEA,GAAKrpD,MAAMC,QAAQqpD,GACnB,GAAyB,iBAAdA,EAAO,IAAwC,iBAAdA,EAAO,GAInD,IAAK,MAAM8B,KAAS9B,EAClB6B,GAAgBC,EAAO/B,QAJvBA,EAAMC,EAAO,GAAIA,EAAO,GAM5B,C,eCfO,MAAM+B,GACG,UADHA,GAED,UAFCA,GAGE,UAHFA,GAIK,UAJLA,GAKH,UALGA,GAMI,UCQXC,GAAQ,IAeRC,GAAQ,GAA4Bn3D,KAAKqX,GAAM,IAC/C+/C,GAAS,IAA0Cp3D,KAAKqX,GAAM,IAC9DggD,GAAOr3D,KAAK4X,IAAIu/C,IAqEhBt9B,GAAQ,IAAI7zB,IACZq2B,GAAS,IAAIr2B,IC/FZ,MAAMsxD,GAAwB,CAAC,WAAY,SAAU,QAAS,YAMxDC,GAAmC,CAC9CC,SAAU,UACVC,OAAQ,UACRC,MAAO,UACPC,SAAU,WAqBCC,GAAqC,CAChDJ,SAAU,GACVC,OAAQ,GACRC,MAAO,GACPC,SAAU,KAICE,GAAoC,CAC/CL,SAAU,WACVC,OAAQ,SACRC,MAAO,QACPC,SAAU,+CAcCG,GAAoC,CAAC,EAAG,GC5CrD,SAASpsB,GAAU7vB,EAAcqhB,GAC/B,OAAa,MAATA,EAAsBrhB,EACnB,GAAGA,MAASqhB,EAAMh9B,eAAe,QAAS,CAC/CC,sBAAuB,EACvBC,sBAAuB,OAE3B,CAmCO,MAAM23D,GAAwD,CACnEvxB,MAAO,UACPH,MAAO,UACPC,IAAK,WAIA,SAAS0xB,KACd,MAAO,CACL,QACA,CAAC,MAAO,aACL9xB,GAAc8d,QAASx6B,GAAU,CAACA,EAAOuuC,GAAyBvuC,KACrEuuC,GAAyBvxB,MAE7B,CC3DA,MAMMyxB,GAAyB,CAC7B,SACA,eACA,cACA,MACA,YACA,YAyBK,SAASC,GACdh0C,EACArjB,EACAs3D,GAiBA,OAAOF,GAAS32D,KAfFyqD,IACZ,MAAMqM,EAtBV,SAAmB5gD,EAAWC,EAAWs0C,GAEvC,MAAMsM,EAAQtM,EAAM9+C,SAAS,SACzBuK,EArBa,EAsBbu0C,EAAM9+C,SAAS,UACbuK,EAvBW,EAHG,IA2BdA,EAAI8gD,IACJtH,EAAQjF,EAAMl/C,WAAW,UAAY4K,EA3BxB,IA2B2CA,EAC9D,MAAO,CAAE4gD,QAAOE,OAAQF,EA7BJ,IA6B2BrH,QAAOwH,MAAOxH,EA5B1C,IA6BrB,CAaiByH,CAAUv0C,EAAK1M,EAAG0M,EAAKzM,EAAGs0C,GACjCntB,EACJw5B,EAAKC,OAAS,GACdD,EAAKpH,OAAS,GACdoH,EAAKG,QAAU13D,EAAMw6B,SACrB+8B,EAAKI,OAAS33D,EAAMy6B,OAChBo9B,IACFP,GACFC,EAAKC,MAAQF,EAASI,QACtBH,EAAKG,OAASJ,EAASE,OACvBD,EAAKpH,MAAQmH,EAASK,OACtBJ,EAAKI,MAAQL,EAASnH,MACxB,OAAOpyB,IAAW85B,KAEU,QAChC,CCAA,MAAMC,GAAY,IC5EX,M,sBACGz2C,OAAS,EACT02C,iBAAmB,EACnBC,WAAa,IAAI91D,IACjB6gC,WAAa,IAAI7gC,IACjB+1D,iBAAkB,EAClB7gC,QAIH,GAEL,WAAAC,CACE,EAAyB,EACzB,EAAmC,G,KADlBxG,MAAAA,E,KACAqnC,gBAAAA,CAChB,CAEHC,OAAAA,CACEH,EACAj1B,G,IACAh5B,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAwC,CAAEkuD,iBAAiB,GAE3D,MAAMG,EAAW,IAAIl2D,IAAI,IAAIs1B,KAAKwgC,cAAexgC,KAAKuL,aACtDvL,KAAKwgC,WAAa,IAAI91D,IAAI81D,GAC1BxgC,KAAKuL,WAAa,IAAI7gC,IAAI6gC,GAC1BvL,KAAKygC,gBAAkBluD,EAAQkuD,gBAC/B,IAAK,MAAMI,IAAQ,IAAI7gC,KAAKJ,UAExBghC,EAASj2D,IAAIk2D,EAAKpyD,MACjBuxB,KAAKwgC,WAAW71D,IAAIk2D,EAAKpyD,MACzBuxB,KAAKuL,WAAW5gC,IAAIk2D,EAAKpyD,MAE1BoyD,EAAKznD,SAGT4mB,KAAK8gC,OACP,CAEAC,QAAAA,CAAYtyD,EAAauyD,EAAwB5oD,GAC/C,OAAO,IAAIN,QAAQ,CAACC,EAASkpD,KAC3B,MAAMJ,EAAO,CACXpyD,MACA2K,OAAQ,KACN4mB,KAAKJ,QAAUI,KAAKJ,QAAQ92B,OAAQ4V,GAAMA,IAAMmiD,GAChDzoD,GAAQkC,oBAAoB,QAASumD,EAAKznD,QAC1C6nD,EAAO,IAAIC,aAAa,yBAA0B,gBAEpD/2C,MAAQohB,IACNnzB,GAAQkC,oBAAoB,QAASumD,EAAKznD,QAC1C4mB,KAAKnW,SACD0hB,GAAYvL,KAAKugC,mBAErBzoD,QAAQC,UACLxS,KAAKy7D,GACLz7D,KAAKwS,EAASkpD,GACd11C,QAAQ,KACPyU,KAAKnW,SACD0hB,GAAYvL,KAAKugC,mBACrBvgC,KAAK8gC,YAIT1oD,GAAQE,QACVuoD,EAAKznD,UAGPhB,GAAQiB,iBAAiB,QAASwnD,EAAKznD,OAAQ,CAAE+nD,MAAM,IACvDnhC,KAAKJ,QAAQx1B,KAAKy2D,GAClB7gC,KAAK8gC,UAET,CAEA5sC,QAAAA,GACE,MAAMktC,EAAmBphC,KAAKJ,QAAQ92B,OACnC4V,GAAMshB,KAAKuL,WAAW5gC,IAAI+T,EAAEjQ,OAASuxB,KAAKwgC,WAAW71D,IAAI+T,EAAEjQ,MAC5DzD,OACF,MAAO,CACL6e,OAAQmW,KAAKnW,OACb02C,iBAAkBvgC,KAAKugC,iBACvBc,OAAQrhC,KAAKJ,QAAQ50B,OACrBs2D,iBAAkBthC,KAAKJ,QAAQ50B,OAASo2D,EACxCA,mBACAV,gBAAiB1gC,KAAKygC,gBAAkBzgC,KAAK3G,MAAQ2G,KAAK0gC,gBAE9D,CAEQI,KAAAA,GACN,KAAO9gC,KAAKnW,OAASmW,KAAK3G,OAAO,CAE/B,IAAIoH,EAAQT,KAAKJ,QAAQr1B,UACtBmU,IAAOshB,KAAKuL,WAAW5gC,IAAI+T,EAAEjQ,MAAQuxB,KAAKwgC,WAAW71D,IAAI+T,EAAEjQ,MAE9D,GAAIgyB,EAAQ,EAAG,CAGb,GAAIT,KAAKugC,mBAAqBvgC,KAAKygC,gBAAkBzgC,KAAK3G,MAAQ2G,KAAK0gC,iBACrE,OACFjgC,EAAQ,CACV,CACA,MAAMogC,EAAO7gC,KAAKJ,QAAQ2hC,OAAO9gC,EAAO,GAAG,GAC3C,IAAKogC,EAAM,OACXA,EAAK12C,MAAM6V,KAAKuL,WAAW5gC,IAAIk2D,EAAKpyD,OAASuxB,KAAKwgC,WAAW71D,IAAIk2D,EAAKpyD,KACxE,CACF,GD3BqC,EAAG,GACpC+yD,GAAY,IAAI5oD,WAAWjL,IACjComB,GAAmB,YAAa,IAAMusC,GAAUpsC,YAKhD,MAAMutC,GAAc,oCAEpB,SAASC,GAAWC,GAClB,OAAOA,EAASr2D,MAAM,KAAK,IAAM,EACnC,CAEA,SAASs2D,GAAcD,GAGrB,OAAOA,EAASr2D,MAAM,KAAK,EAC7B,CAGA,SAASu2D,GAAS92C,GAEhB,OAAIA,aAAgBhb,MAAcgb,EAAK1Z,KAChC,UACT,CAEA,MAAMywD,GAAgC78D,MAAO2R,IAC3C,GAAoB,UAAhBA,EAAO3I,KAAkB,CAC3B,MAAMyG,EAAQkC,EAAO3M,IAAIyK,MAAM+sD,IAC/B,GAAI/sD,EAAO,CACT,MAAMq0C,EAAS6Y,GAAcltD,EAAM,IACnC,GAAIgtD,GAAWhtD,EAAM,IAAIF,WAAW,WAAY,CAC9C,MAAMmnB,EAAI3hB,SAAStF,EAAM,IACnByK,EAAInF,SAAStF,EAAM,IACnB0K,EAAIpF,SAAStF,EAAM,IACzB,IACE,IAAIkoB,QAAejJ,GAAK,cAAe,IJY1C1uB,eACLgF,EACA0xB,EACAxc,EACAC,GAEA,MAAMmmB,EAAU,GAAGt7B,KAAO0xB,KAAKxc,KAAKC,IACpC,IAAI6kB,EAAQzC,GAAMlyB,IAAIi2B,GACtB,GAAItB,EACFvQ,GAAI,mBACJ8N,GAAM3mB,OAAO0qB,GACb/D,GAAMlxB,IAAIi1B,EAAStB,OACd,CACL,IAAInZ,EAAQkZ,GAAO10B,IAAIi2B,GAClBza,IACH4I,GAAI,kBACJ5I,EA5BN7lB,eAAsBgF,EAAa0xB,EAAWxc,EAAWC,GACvD,MAAOyc,EAAMkmC,EAAMC,EAAMlmC,GAAQJ,GAAiBC,EAAGxc,EAAGC,GAClD6iD,GAASD,EAAOnmC,GAAQgjC,GACxB70B,QAAgBjH,GACpB94B,EACA,CAAC4xB,EAAOomC,EAAOF,EAAOE,EAAOD,EAAOC,EAAOnmC,EAAOmmC,GAClDpD,IACAA,KAEF,OAAO,IAAI15B,UA7EN,SACLxJ,EACAqH,EACAC,EACAi/B,G,IACAC,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAxB0B,EA0B1B,MAAM59B,EAAO,IAAIC,kBAAkBxB,EAAUC,EAAS,GAChDkH,EAAMnH,EAAU,EACtB,IAAK,IAAI99B,EAAI,EAAGA,EAAI+9B,EAAQ/9B,IAC1B,IAAK,IAAIM,EAAI,EAAGA,EAAIw9B,EAASx9B,IAAK,CAChC,MAAM+kC,GAAUrlC,EAAI,GAAKilC,GAAO3kC,EAAI,GAC9B8D,EAAIqyB,EAAE4O,EAASJ,EAAM,GACrB5gC,EAAIoyB,EAAE4O,EAASJ,GACf/hB,EAAIuT,EAAE4O,EAASJ,EAAM,GACrBr+B,EAAI6vB,EAAE4O,EAAS,GACf2O,EAAIvd,EAAE4O,EAAS,GACfzb,EAAI6M,EAAE4O,EAASJ,EAAM,GACrBiC,EAAIzQ,EAAE4O,EAASJ,GACf7/B,EAAIqxB,EAAE4O,EAASJ,EAAM,GACrBrgC,EAA0B,GAAnB5E,EAAI89B,EAAUx9B,GAC3B,IAAK+F,OAAOi4B,SAASl6B,EAAIC,EAAI6e,EAAItc,EAAIotC,EAAIpqB,EAAIsd,EAAI9hC,KAAOiB,OAAOi4B,SAAS7H,EAAE4O,IACxE,SAEF,MAAM63B,GAAQh6C,EAAI,EAAI8wB,EAAI5uC,GAAKhB,EAAI,EAAIwC,EAAIgjB,KAAO,EAAIozC,GAChDG,GAAQvzC,EAAI,EAAIsd,EAAI9hC,GAAKhB,EAAI,EAAIC,EAAI6e,KAAO,EAAI85C,GAChDI,EAAU36D,KAAK+xD,KAAKyI,EAAex6D,KAAKgiC,MAAMy4B,EAAMC,IACpDE,EAAW56D,KAAKkjD,MAAMwX,GAAOD,GAI7B1+B,EAFJ/7B,KAAK4X,IAAIu/C,IAASn3D,KAAK4X,IAAI+iD,GAC3B36D,KAAK6X,IAAIs/C,IAASn3D,KAAK6X,IAAI8iD,GAAW36D,KAAK4X,IAAIw/C,GAASwD,GACjCvD,GACrBt7B,EAAW,EACba,EAAKz6B,EAAM,GAAKnC,KAAK8S,IAAI,KAAOipB,EAAWs7B,GAAQ,IAhDnC,MAkDhBz6B,EAAKz6B,GAAO,IACZy6B,EAAKz6B,EAAM,GAAK,IAChBy6B,EAAKz6B,EAAM,GAAK,IAChBy6B,EAAKz6B,EAAM,GAAKnC,KAAK8S,IAAI,IAAK,GAAa,EAAIukD,IAAS,IApD1C,IAsDlB,CAEF,OAAOz6B,CACT,CAkCuBi+B,CAAQx4B,EAAS60B,GAAOA,GA/BxC,SAAwBljC,EAAWvc,GACxC,MAAOyc,EAAMkmC,EAAMC,EAAMlmC,GAAQJ,GAAiBC,EAAG,EAAGvc,GAClDqjD,GAAWV,EAAOjmC,GAAQ,EAC1B4mC,EAAe,EAAI/6D,KAAK+xD,KAAK/xD,KAAKgyD,IAAI8I,EAAU,UAAY96D,KAAKqX,GAAK,EAC5E,OAASgjD,EAAOnmC,GAAQgjC,GAASl3D,KAAK4X,IAAImjD,EAC5C,CA0BsDC,CAAehnC,EAAGvc,IAAKy/C,GAAOA,GACpF,CAkBc36B,CAAOj6B,EAAK0xB,EAAGxc,EAAGC,GAC1B4kB,GAAO1zB,IAAIi1B,EAASza,IAEtB,IACEmZ,QAAcnZ,CAChB,CAAE,QACAkZ,GAAOnpB,OAAO0qB,EAChB,CACA,KAAO/D,GAAMl7B,MA9CC,IA8CkB,CAC9B,MAAMo7B,EAASF,GAAMpB,OAAOjN,OAAO5hB,MACnC,QAAekB,IAAXivB,EAAsB,MAC1BF,GAAM3mB,OAAO6mB,EACf,CACAF,GAAMlxB,IAAIi1B,EAAStB,EACrB,CACA,OAAOpH,kBAAkBoH,EAC3B,CI5CuD2+B,CAAiB7Z,EAAQptB,EAAGxc,EAAGC,IAC5E,MAAMk+C,EAAOa,KAEb,OADIb,IAAM1gC,QAAewhC,GAAgBxhC,EAAQ0gC,EAAM3hC,EAAGxc,EAAGC,IACtD,CAAEzI,KAAMimB,EACjB,CAAE,MAAO7R,GAGP,OADA2I,GAAI,mBAAmBmuC,GAAS92C,MACzB,CAAEpU,WAAYkmB,kBAAkB,IAAIsI,UAAU,IAAK,MAC5D,CACF,CACA,MAAM09B,EAAS1mC,GAAoB4sB,GAC7BptB,EAAI3hB,SAAStF,EAAM,IACnByK,EAAInF,SAAStF,EAAM,IACnB0K,EAAIpF,SAAStF,EAAM,IACzB,IAAKmuD,GAAUnB,GAAWhtD,EAAM,IAAIZ,SAAS,SAI3C,OADA4f,GAAI,+BACG,CAAE/c,WAAYkmB,kBAAkB,IAAIsI,UAAU,IAAK,OAE5D,IAAK09B,GAAUnB,GAAWhtD,EAAM,IAAIZ,SAAS,aAK3C,OADA4f,GAAI,uCACG,CAAE/c,WAAYkmB,kBAAkB,IAAIsI,UAAU,IAAK,OAE5D,GAAI09B,EACF,IACEnvC,GAAI,qBACJ,IAAIkJ,QAAejJ,GAAK,aAAc,IrE6VzC1uB,eACLk5B,EACAC,EACAzC,EACAxc,EACAC,GAEA,MAAMgd,QAAeC,GAAmB8B,EAAaC,EAAWzC,EAAGxc,EAAGC,GACtE,OAAOuU,GAAK,cAAe,IAAMkJ,kBAAkBT,GACrD,CqEtWsD0mC,CAAgB/Z,EAAQ8Z,EAAQlnC,EAAGxc,EAAGC,GAAI,CACpFuc,IACAxc,IACAC,MAEF,MAAMk+C,EAAOa,KAIb,OAHIb,IACF1gC,QAAejJ,GAAK,YAAa,IAAMyqC,GAAgBxhC,EAAQ0gC,EAAM3hC,EAAGxc,EAAGC,KAEtE,CAAEzI,KAAMimB,EACjB,CAAE,MAAO7R,GAQP,OADA2I,GAAI,qBAAqBmuC,GAAS92C,MAC3B,CAAEpU,KAAM,KACjB,CAEJ,CACF,CAEA,IAAIg4C,EACJ,IACsB,UAAhB/3C,EAAO3I,MAAkBylB,GAAI,wBACb,SAAhB9c,EAAO3I,MAAiBylB,GAAI,0BAChCi7B,QAAeh7B,GAAK,gBAAgB/c,EAAO3I,QAAS,KAAM80D,EAAAA,GAAAA,IAAYnsD,GACxE,CAAE,MAIA,OAHA8c,GAAI,wCAGG,CAAE/c,KAAM,KACjB,CACA,GAAoB,SAAhBC,EAAO3I,MAAmB0gD,EAAOh4C,KAAM,CACzC,MAAMqsD,EAAU,EAAQrsD,KAAassD,SAAW,EAC/CtU,EAAOh4C,KAAassD,QAAUt7D,KAAK8S,IAAIuoD,EAAS,EACnD,CACA,GAAoB,UAAhBpsD,EAAO3I,KAAkB,CAC3B,MAAMqvD,EAAOa,KACb,GAAIb,EAAM,CACR,MAAM5oD,EAAQkC,EAAO3M,IAAIyK,MAAM+sD,IAC/B,GAAI/sD,EAAO,CACT,MAAMinB,EAAI3hB,SAAStF,EAAM,IACnByK,EAAInF,SAAStF,EAAM,IACnB0K,EAAIpF,SAAStF,EAAM,IACzBi6C,EAAOh4C,WAAagd,GAAK,YAAa,IACpCyqC,GAAgBzP,EAAOh4C,KAAqB2mD,EAAM3hC,EAAGxc,EAAGC,GAE5D,CACF,CACF,CACA,OAAOuvC,GAsCTuU,KAAAA,YAAuB,MApCI,CACzBtsD,EACAusD,KAEA,MAAMC,EAAQpwC,GAAY,iBACpBte,EAAwB,UAAhBkC,EAAO3I,KAAmB2I,EAAO3M,IAAIyK,MAAM+sD,IAAe,KAClEhzD,EAAMmzD,GAAcltD,EAAQA,EAAM,GAAKkC,EAAO3M,IAAI4K,QAAQ,YAAa,KAC7E,OAAOyrD,GACJS,SACCtyD,EACAxJ,UACEm+D,IACA,IACE,GAAID,GAAY/qD,OAAOE,QAAS,MAAM,IAAI4oD,aAAa,YAAa,cACpE,MAAMvS,QAAemT,GAAUlrD,GAC/B,GAAIusD,GAAY/qD,OAAOE,QAErB,KADoB,UAAhB1B,EAAO3I,MAAoB0gD,EAAOh4C,MAAM,EAAQA,KAAqBqmB,QACnE,IAAIkkC,aAAa,YAAa,cAEtC,IAAKvS,EAAOh4C,KAAM,MAAM,IAAI5G,MAAM,kCAElC,OADAyxD,GAAU3mD,OAAOjE,EAAO3M,KACjB0kD,CACT,CAAE,MAAOh+C,GAOP,MALGwyD,GAAY/qD,OAAOE,SAClB3H,aAAiBZ,OAAwB,eAAfY,EAAMU,MAElCmwD,GAAUlxD,IAAIsG,EAAO3M,IAAKwE,GAEtBkC,CACR,GAEFwyD,GAAY/qD,QAEbmT,QAAQ,IAAM63C,EAAM,gBAGzBF,KAAAA,YAAuB,WAAYG,GAAAA,GAGnC,MAAMC,GAAqB,CACzB3Z,SAAU,OACVC,UAAW,KACX58C,KAAM,IAMR,IAAIu2D,IAAmB,EAMvB,MAAMC,GAA0D,CAC9D,aAAc,CACZ,QACA,CAAC,MAAO,QACR,WACAtE,GAAWC,SACX,SACAD,GAAWE,OACX,QACAF,GAAWG,MAGXH,GAAWI,UAEb,aAAc,CAAC,QAAS,CAAC,MAAO,QAAS,QAAS,IAAK,KAIvD,eAAgB,CACd,QACA,CAAC,MAAO,QACR,WACAC,GAAaJ,SACb,SACAI,GAAaH,OACb,QACAG,GAAaF,MACbE,GAAaD,WAiBF,SAASmE,GAAQ,G,IAAA,cAAEC,EAAa,OAAEnX,GAAjB,EAC9B,MAAM,MAAEtO,EAAK,SAAEmK,GAAaE,MACrBqb,EAAaC,IAAkB/+D,EAAAA,EAAAA,WAAS,GACzCg/D,GAAiBv8B,EAAAA,EAAAA,QAAO2W,GAC9B4lB,EAAep5B,QAAUwT,GACzBn5C,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,GAAIhgD,GAAOkyD,EAAa,OE5SrB,SAAkClyD,EAAUqyD,GACjD,IACG,CAAC,YAAa,aAAahwD,SAAS2d,OAAOC,SAASC,WACV,MAA3CC,aAAaC,QAAQ,kBAErB,MAAO,OACT,MAAM67B,EAASj8B,OACT6K,EAAS7qB,EAAI4gD,YACb0R,EAAKznC,EAAOG,WAAW,WAAaH,EAAOG,WAAW,SACtDunC,EAAQD,GAAIE,aAAa,6BACzBC,EAAWH,GAAMC,EAAQD,EAAGI,aAAaH,EAAMI,yBAA2B,KAC1E5uC,EAAM,CACV6uC,YAAAA,IACSzzD,OAAOwvB,KAAK3uB,EAAI6yD,WAAWv2D,SAC/BjF,OAAQkB,GAAOA,EAAGwK,WAAW,SAC7B/C,IAAKzH,IACJ,MAAMuE,EAASkD,EAAI8yD,UAAUv6D,GAC7B,MAAO,CACLA,GAAIuB,OAAOvB,EAAGsB,MAAM,KAAK,IACzB23D,QAAS10D,EAAO00D,QAChBuB,QAASj2D,EAAOi2D,QAChB3K,OAAQtrD,EAAOsrD,UAIvB5b,KAAAA,GACE,MAAMA,EAAQ6lB,IACd,MAAO,CACL7b,MAAOhK,EAAMgJ,eACbD,aAAc/I,EAAM+I,aACpBQ,SAAUvJ,EAAMuJ,SAChBid,YAAahzD,EAAIizD,iBAErB,EACAC,OAAAA,CAAQC,EAA0B53D,GAChCyE,EAAIozD,OAAO,CAAED,SAAQ53D,OAAM4kD,QAAS,EAAGD,MAAO,GAChD,EACAz9B,QAAAA,GACE,MAAM+pB,EAAQ6lB,IACRxnC,EAAS7qB,EAAI4gD,YACnB,MAAO,CACLrL,aAAc/I,EAAM+I,aACpBiB,MAAOhK,EAAMgJ,eACbO,SAAUvJ,EAAMuJ,SAChBX,eAAgB5I,EAAM4I,eACtBpuB,gBAAiBwlB,EAAMxlB,gBACvBmsC,OAAQnzD,EAAIqzD,YAAYC,UACxB/3D,KAAMyE,EAAIuzD,UACVlzC,uBAAsBA,GACtBmzC,oBAAqBr0D,OAAOC,QAAQY,EAAI6yD,WAAWv2D,SAASjF,OACzD,I,IAACkB,EAAIuE,GAAO,E,OACTvE,EAAGwK,WAAW,SAAW,gCAAgCxM,KAAKgH,KAAKY,UAAUrB,MACjFvD,OACFsxB,OAAQ,CAAEte,MAAOse,EAAOte,MAAOC,OAAQqe,EAAOre,QAC9CimD,WACAgB,WAAYt0D,OAAOwvB,KAAK3uB,EAAI6yD,WAAWv2D,SAASjF,OAAQkB,GAAOA,EAAGwK,WAAW,SAC7E2wD,sBAAuBv0D,OAAOC,QAAQY,EAAI6yD,WAAWv2D,SAASjF,OAC3D,I,IAACkB,EAAIuE,GAAO,E,OACXvE,EAAGwK,WAAW,SACd,uCAAuCxM,KAAKgH,KAAKY,UAAUrB,MAC7DvD,OAEN,GAGF,OADA0iD,EAAO0X,iBAAmB5vC,EACnB,KACDk4B,EAAO0X,mBAAqB5vC,UAAYk4B,EAAO0X,iBAEvD,CFwOmCC,CAAyB5zD,EAAK,IAAMoyD,EAAep5B,UACjF,CAAC8hB,EAAQoX,IACZ,MAAO2B,EAAUC,IAAe1gE,EAAAA,EAAAA,UAAwB,OACjD2gE,EAAYC,IAAiB5gE,EAAAA,EAAAA,UAAwB,KAEpDkhC,QAASmgB,EAAcnsB,QAAS2rC,GAAwB7uB,MACxD9Q,QAASogB,EAAUpsB,QAAS4rC,GAAoBnd,GAAY,aAC5DziB,QAASqgB,EAAarsB,QAAS6rC,GAAuBpd,GAAY,eAClEziB,QAASsgB,EAAgBtsB,QAAS8rC,GAA0Brd,GAAY,kBACxEziB,QAASugB,EAAQvsB,QAAS+rC,GAAkBtd,GAAY,UACxDziB,QAASwgB,EAAexsB,QAASgsC,GAAyBvd,GAAY,iBAE5EziB,QAASygB,EACTz7B,KAAMi7C,EACNhe,QAASie,GTpJN,SACLvL,EACAiB,G,IACAlpC,IAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,KAAAA,UAAAA,GAEA,MAAOsT,EAAS+Q,IAAcjyC,EAAAA,EAAAA,UAA2C,OAClEi2C,EAAOqd,IAAYtzD,EAAAA,EAAAA,WAAS,IAC5BkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,UAAwB,OACzCmjD,EAASke,IAAcrhE,EAAAA,EAAAA,UAAwB,MAEhDklC,EAAU2xB,GAAmBhB,EAAQiB,GACrC9uB,EAAM9C,GAAS8C,KAAO,KACtBC,EAAM/C,GAAS+C,KAAO,KAsF5B,OApFAhoC,EAAAA,EAAAA,WAAU,KAKR,IAAK2tB,IAAiBoa,IAAQC,EAK5B,OAJAgK,EAAW,MACXqhB,GAAS,GACTjtC,EAAQ,WACRg7C,EAAW,MAIb,MAAMC,EAAQlK,IACZnlB,EAAWmlB,EAASl2B,SACpBmgC,EArJC,SACLn8B,EACA4C,EACAy5B,GAEA,IAAKr8B,EAAS,OAAO,KACrB,IAAK4C,EAAS,MAAO,6CAErB,MAAM05B,EAAQ15B,EAAQE,IAAI3jC,MAAM,EAAG,IAC7Bo9D,EAAW35B,EAAQG,IAAI5jC,MAAM,EAAG,IAChCkhB,EAAU,4BAA4B1hB,GAAMikC,EAAQE,mBAAmBnkC,GAAMikC,EAAQG,QAE3F,OAAI/C,EAAQ+C,KAAOu5B,GAASt8B,EAAQ8C,KAAOy5B,EAClC,wCAAwCl8C,IAE7C2f,EAAQ8C,IAAMw5B,GAASt8B,EAAQ+C,IAAMw5B,EAChC,0DAA0Dl8C,IAEzC,IAAnBg8C,EAAuB,uCAAyC,IACzE,CAkIiBG,CAAgB,CAAE15B,MAAKC,OAAOmvB,EAAStvB,QAASsvB,EAASnxD,UAGhEixD,EAAUv6B,GAAMlyB,IAAIi2B,GAAQsH,EAAKC,IACvC,GAAIivB,EAKF,OAHA7wC,EAAQ,MACRitC,GAAS,QACTgO,EAAKpK,GAIP,IAAI5wC,GAAa,EAuBjB,OAtBAgtC,GAAS,GAGTjtC,EAAQ,MAIR4wC,GAAYjvB,EAAKC,GACdvnC,KAAM02D,IACA9wC,GAAYg7C,EAAKlK,KAEvB3wC,MAAM,KACAH,IACH2rB,EAAW,MACXovB,EAAW,MACXh7C,EAAQ,iDAGXK,QAAQ,KACFJ,GAAYgtC,GAAS,KAGvB,KACLhtC,GAAa,IAEd,CAAC0hB,EAAKC,EAAKra,KAEd3tB,EAAAA,EAAAA,WAAU,KAGR,IAAK2tB,IAAiBkpC,GAAgB7gB,EAAO,OAC7C,IAAI0rB,GAAU,EAoBd,MAlBK,WACH,IAAK,MAAMC,KAAQhM,GAAgBC,EAAQiB,GAAczyD,MAAM,EAhKrD,GAgKkE,CAC1E,GAAIs9D,EAAS,OACb,MAAME,EAAchL,GAAmBhB,EAAQ+L,GAC/C,GAAKC,IACDllC,GAAM72B,IAAI46B,GAAQmhC,EAAY75B,IAAK65B,EAAY55B,MACnD,UAGQgvB,GAAY4K,EAAY75B,IAAK65B,EAAY55B,IACjD,CAAE,MAIF,CACF,CACD,EAhBI,GAkBE,KACL05B,GAAU,IAEX,CAAC9L,EAAQiB,EAAclpC,EAAcqoB,IAEjC,CAAE/U,UAAS+U,QAAO/vB,OAAMi9B,UACjC,CSkDM2e,CACF1oB,EAAMgJ,eACNhJ,EAAM+I,aACN/I,EAAM3vC,OAAOk4C,oBAEf1hD,EAAAA,EAAAA,WAAU,KACRsjD,EAAS,CAAEn6C,KAAM,eAAgBstB,KAAM,oBAAqBxQ,KAAMi7C,KACjE,CAAC5d,EAAU4d,KACdlhE,EAAAA,EAAAA,WAAU,KACRsjD,EAAS,CACPn6C,KAAM,kBACNstB,KAAM,oBACNysB,QAASie,KAEV,CAAC7d,EAAU6d,IACd,MAAM,SAAEx+C,GAAaoS,GAAiBokB,EAAM+I,aAAc/I,EAAMxlB,kBACxDhR,SAAU6kC,GAAgBlyB,GAChC6jB,EAAM+I,aACN/I,EAAMxlB,gBACN8vB,GAAetK,EAAO,QAIlB2oB,GAAmBl4D,EAAAA,EAAAA,SACvB,IACEuvC,EAAM3vC,OAAOy3C,gBFzTZ,SACLt+B,GAEA,MAAMue,EAA8B,GAEpC,IAAK,MAAM9hB,KAAMuD,EAAU,CACzB,MAAMoE,EAAO3H,EAAG2xC,iBAChB,IAAKhqC,EAAM,SAEX,MAAOwa,EAAK3W,GAAOkW,GAAU/Z,EAAK,GAAIA,EAAK,IACrC9kB,EAA8B,CAClCyc,KAAMU,EAAG4D,eACT+c,MAAO3gB,EAAGuB,kBAAoB,KAC9BohD,WAAY3iD,EAAGoB,cAAgB,KAC/BtV,OAAQ89B,GAAY5pB,EAAGoB,aAAcsoB,IACrC7pB,MAAOsvB,GAAUnvB,EAAG4D,eAAgB5D,EAAGuB,kBAAoB,OAG7DugB,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAYpyC,EACZm/B,SAAU,CAAEj4B,KAAM,QAASsQ,YAAa,CAAC8nB,EAAK3W,KAElD,CAEA,MAAO,CAAEzhB,KAAM,oBAAqB+3B,WACtC,CEgSU8gC,CAAwBr/C,GACxB,CAAExZ,KAAM,oBAA8B+3B,SAAU,IACtD,CAACve,EAAUw2B,EAAM3vC,OAAOy3C,mBAO1BjhD,EAAAA,EAAAA,WAAU,KrEpQL,IAA0B6+B,IqEqQZsa,EAAMta,SrEpQzBrF,GAAgBqF,GqEqQb,CAACsa,EAAMta,YACV7+B,EAAAA,EAAAA,WAAU,KrEpPL,IAA0B0tB,IqEqPa,aAAzByrB,EAAM4I,erEpPzBtoB,GAAgB/L,GqEqPb,CAACyrB,EAAM4I,iBAIV,MAAMkgB,EAA4C,aAAzB9oB,EAAM4I,eAAgC,MAAQ5I,EAAM4I,gBAK3E9gB,QAASihC,EACTlsB,MAAOmsB,EAAkB,uBACzBpO,GACEb,GAAiB,CACnBvwC,WACAnf,WAAYy+D,EACZriE,QAASu5C,EAAM3vC,OAAO03C,eAAiB/H,EAAM6H,YAAY96C,OAAS,KAK5D+6B,QAASmhC,EAAkBpsB,MAAOqsB,GAAmBhO,GAAmB,CAC9E1xC,WACAnf,WAAY21C,EAAM4I,eAClBuS,SfzV8B,Ge0V9B10D,QAASu5C,EAAM3vC,OAAO23C,iBAAmBhI,EAAM6H,YAAY96C,OAAS,IAUhEo8D,EAAwBnpB,EAAM3vC,OAAO03C,cAEvCihB,EACE,2BAC2B,IAA3BpO,EACE,gGACAA,EAAyB,EACvB,GAAGA,kGACH,KAPN,KAQEwO,EACJppB,EAAM3vC,OAAO23C,iBAAmBkhB,EAAiB,6BAA+B,MAClFriE,EAAAA,EAAAA,WAAU,KACRsjD,EAAS,CAAEn6C,KAAM,kBAAmBstB,KAAM,gBAAiBysB,QAASof,KACnE,CAAChf,EAAUgf,KACdtiE,EAAAA,EAAAA,WAAU,KACRsjD,EAAS,CAAEn6C,KAAM,kBAAmBstB,KAAM,kBAAmBysB,QAASqf,KACrE,CAACjf,EAAUif,IAId,MAAMjgB,GAAkB3iD,EAAAA,GAAAA,IAAmBw5C,EAAM3vC,OAAOm4C,WACxD3hD,EAAAA,EAAAA,WAAU,KACRsjD,EAAS,CAAEn6C,KAAM,uBAAwBtJ,QAASyiD,KACjD,CAACgB,EAAUhB,IACd,MAAMkgB,GAAkBj/D,EAAAA,GAAAA,IAAY41C,EAAM4I,gBACpC0gB,GAAe74D,EAAAA,EAAAA,SACnB,IACEuvC,EAAM3vC,OAAOm4C,UACT99C,EAAAA,GAAAA,IACEy+C,EACAnJ,EAAM+I,aACN7uB,GAAqB8lB,EAAM+I,cAC3BsgB,GAEF,KACN,CAAClgB,EAAiBnJ,EAAM3vC,OAAOm4C,SAAUxI,EAAM+I,aAAcsgB,IAEzDE,GAAsB9qD,EAAAA,EAAAA,aACzB1S,IACC,MAAMyH,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IACE,QAAShgD,GAAK8yD,UAAUv6D,IAAOyH,EAAIg2D,eAAez9D,EACpD,CAAE,MACA,OAAO,CACT,GAEF,CAACuiD,IAEGmb,GAAiBh5D,EAAAA,EAAAA,SACrB,IACE64D,EACI,CACEI,UAAWJ,EAAaK,WACxBC,aAAcN,EAAaO,cAC3Bz7D,QAASk7D,EAAal7D,QACtBX,SAAiC,iBAAvB67D,EAAa9+D,OAEzB,KACN,CAAC8+D,KAKIQ,EAAOC,IAAYnjE,EAAAA,EAAAA,UAAwD,MAG5EojE,GAASv5D,EAAAA,EAAAA,SACb,KAAO,CAAE03C,cAAaE,SAAQD,iBAAgBE,kBAC9C,CAACH,EAAaE,EAAQD,EAAgBE,IAElC2hB,EGzaD,SAAyB,G,IAAA,KAC9Br8C,EAAI,SACJpE,EAAQ,aACRy+B,EAAY,eACZW,EAAc,SACdJ,EAAW,KAAI,OACfwhB,GAN8B,EAQ9B,MAAOzU,EAAS2U,IAActjE,EAAAA,EAAAA,UAA8B,MA+B5D,OA7BAC,EAAAA,EAAAA,WAAU,KAER,GADAqjE,EAAW,OACNt8C,EAAM,OAGX,IAAIu8C,GAAc,EAmBlB,OrBiCGnjE,eAAgC,G,IAAA,IACrCohC,EAAG,IACH3W,EAAG,SACHjI,EAAQ,aACRy+B,EAAY,eACZW,EAAc,UACdrsB,EAAS,KACTxtB,EAAO,GAAE,SACTy5C,EAAW,KAAI,OACfwhB,EAAS,CAAC,GAT2B,EAarC,MAAM70D,EAASskD,GAAerxB,EAAK3W,EAAK,CAAEw2B,kBAAiB+hB,IACrDjU,EAAU5gD,GAAQgY,KAAO,KACzB0oC,EAAO1gD,EAASwkD,GAASxkD,GAAU,KAEnCqQ,EAAUuwC,EAAUvsC,EAASxe,KAAM6C,GAAMA,EAAEgc,iBAAmBksC,QAAWvhD,EACzE41D,EAAcpjE,MAAO+E,IACzB,MAAMC,QAAYuwB,EAAUxwB,GAC5B,IAAKC,EAAK,OAAO,KACjB,IACE,MAAMq+D,QAAgB5e,EAAAA,GAAAA,IAAez/C,EAAK,CAAE0/C,SAAUj6B,EAAKk6B,UAAWvjB,GAAOr5B,GACvEu7D,EAAOD,EAAQt9D,OAAS,EAAIs9D,EAAQ,GAAK,KAC/C,OAAe,MAARC,GAAgBh9D,OAAOi4B,SAAS+kC,GAAQA,EAAO,IACxD,CAAE,MAGA,OAAO,IACT,GAEF,IAAItU,EAA0BxwC,QAAgB4kD,EAAY5kD,EAAQzZ,IAAM,KACxE,MAAMw+D,EAAyB,MAAZvU,EAEnB,IAAIwU,EAAwC,CAAErU,YAAa,KAAM3oD,KAAM,KAAMC,UAAU,GACvF,GAAgB,MAAZuoD,GAAoBxN,EAAU,CAChC,MAAMj2C,QA1EVvL,eACEwhD,EACApgB,EACA3W,EACA1iB,GAEA,IACE,MAAM6e,EAAO,CAAE89B,SAAUj6B,EAAKk6B,UAAWvjB,IAClCU,SAAgB2iB,EAAAA,GAAAA,IAAejD,EAASkhB,UAAW97C,EAAM7e,GAChE,GAAc,MAAV+5B,IAAmBx7B,OAAOi4B,SAASuD,GAAS,OAAO,KACvD,MAAO,CAAE2hC,EAASC,SAAmBjf,EAAAA,GAAAA,IAAejD,EAASohB,aAAch8C,EAAM7e,GACjF,MAAO,CACLinD,SAAUltB,EACVqtB,YAAa7oD,OAAOi4B,SAASklC,GAAWA,EAAU,IAAM,KACxDj9D,KAAMF,OAAOi4B,SAASmlC,GAAcliB,EAASp6C,QAAQs8D,IAAc,KAAQ,KAC3Ej9D,SAAU+6C,EAAS/6C,SAEvB,CAAE,MACA,OAAO,IACT,CACF,CAsDoBk9D,CAAaniB,EAAUpgB,EAAK3W,EAAK1iB,GAC7CwD,IACFyjD,EAAWzjD,EAAEyjD,SACbwU,EAAQ,CAAErU,YAAa5jD,EAAE4jD,YAAa3oD,KAAM+E,EAAE/E,KAAMC,SAAU8E,EAAE9E,UAEpE,CAEA,IAAKsoD,GAA8B,QAAnBnN,GAAwC,MAAZoN,EAC1C,MAAO,CAAED,UAASF,OAAMG,WAAUC,aAAc,KAAMC,OAAQ,QAASsU,GAKzE,GAAuB,aAAnB5hB,EAA+B,CACjC,MAAMgiB,EAAWL,EAAa/kD,GAASoU,kBAAoB,KACrDixC,EAAqB,MAAZD,QAAyBR,EAAYQ,GAAY,KAChE,MAAO,CACL7U,UACAF,OACAG,WACAC,aAAc4U,EACd3U,OAAkB,MAAV2U,EAAiB7U,EAAW6U,EAAS,QAC1CL,EAEP,CAKA,GAAuB,QAAnB5hB,EAA0B,CAC5B,MAAM5gB,EAAUigB,GAAclgB,SAAS/8B,KAAMiwC,GAAMA,EAAEC,YAAY31B,OAASwwC,GACpE+U,EAAW,GAAU5vB,YAAY6vB,WAA2C,KAClF,MAAO,CACLhV,UACAF,OACAG,WACAC,aAAc6U,EACd5U,OAAoB,MAAZ4U,EAAmB9U,EAAW8U,EAAW,QAC9CN,EAEP,CAEA,MAAOngE,SAAoBgjD,GACzB,CAAC,CAAEjlB,MAAK3W,QACRw2B,EDnCG,SACL75C,GAEA,MAAM25B,EAAW,CAAC35B,EAAQ+5C,YAAa/5C,EAAQi6C,OAAQj6C,EAAQg6C,gBAAgBsF,QAC5E8L,IACEA,GAAWzxB,UAAY,IAAIv0B,IAAKynC,IAAO,IACnCA,EACHC,WAAY,IACPD,EAAEC,WACLA,WAAY,IAAK5S,GAAO2S,GAAIJ,UAAWqe,GAAcje,SAI7D,OAAOlT,EAASh7B,OAAS,EAAI,CAAEiD,KAAM,oBAAqB+3B,YAAa,IACzE,CCsBIijC,CAAsB,CAAE/iB,kBAAiB+hB,IACzCj7D,GAEIknD,EAAe5rD,IAAau+C,IAAmB,KAErD,MAAO,CACLmN,UACAF,OACAG,WACAC,eACAC,OAAwB,MAAhBD,EAAuBD,EAAWC,EAAe,QACtDuU,EAEP,CqBjJSS,CAAiB,CACpB7iC,IAAKxa,EAAKwa,IACV3W,IAAK7D,EAAK6D,IACVjI,WACAy+B,eACAW,iBACArsB,UAASA,GACTxtB,KAAMrF,KAAK4c,MAAMsH,EAAK7e,MACtBy5C,WACAwhB,WAEC1iE,KAAMsuB,IACAu0C,GAAaD,EAAWt0C,KAE9BvI,MAAM,KAEA88C,GAAaD,EAAWrQ,MAE1B,KACLsQ,GAAc,IAEf,CAACv8C,EAAMpE,EAAUy+B,EAAcW,EAAgBJ,EAAUwhB,IAErDzU,CACT,CHiYuB2V,CAAgB,CACnCt9C,KAAMk8C,GAAOl8C,MAAQ,KACrBpE,WACAy+B,eACAW,eAAgB5I,EAAM4I,eACtBJ,SAAUihB,EACVO,WAEImB,EAAgBnrB,EAAM6H,YAAY96C,OAAS,KAAO08D,EAClD2B,EAASprB,EAAMoJ,aAAaC,SAAWrJ,EAAMsJ,OAAOD,SAK1DxiD,EAAAA,EAAAA,WAAU,KACJukE,GAAQrB,EAAS,OACpB,CAACqB,KAEJvkE,EAAAA,EAAAA,WAAU,KACR,IAAKijE,EAAO,OACZ,MAAMvS,EAAWx+C,IACf,GAAc,WAAVA,EAAEvI,IAAkB,OAGxB,MAAM/E,EAAOsN,EAAE02C,OAEb12C,EAAEsyD,kBACF5/D,GAAM6/D,UACJ,uFAIJvB,EAAS,OAGX,OADAv2C,OAAOpY,iBAAiB,UAAWm8C,GAC5B,IAAM/jC,OAAOnX,oBAAoB,UAAWk7C,IAClD,CAACuS,IAEJ,MAAMl6D,GAAaW,EAAAA,GAAAA,GAAoByvC,EAAMjwC,SACvCw7D,GAAW96D,EAAAA,EAAAA,SAAQ,KAAMd,EAAAA,GAAAA,GAAgBC,GAAa,CAACA,IAKvD47D,EACJ/D,GACAC,GACAC,GACAC,GACAC,GACAC,GAEFjhE,EAAAA,EAAAA,WAAU,KACR,GAAIy+D,KAAqBI,GAAe8F,EAAe,OACvD,MAAMh4D,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OAEV,MAAMooD,ENnfH,WACL,sDAAGrL,EAAH,gBAEA,IAAIgO,EAAS95B,IACT+5B,EAAS/5B,IACTg6B,GAAS,IACTC,GAAS,IAEb,IAAK,MAAM+M,KAAclb,EACvB,IAAK,MAAMvoB,KAAWyjC,GAAY1jC,UAAY,GAAI,CAChD,MAAME,EAAWD,EAAQC,SACzBw4B,GAAgBx4B,GAAU3nB,YAAa,CAAC8nB,EAAK3W,KACvC2W,EAAMm2B,IAAQA,EAASn2B,GACvB3W,EAAM+sC,IAAQA,EAAS/sC,GACvB2W,EAAMq2B,IAAQA,EAASr2B,GACvB3W,EAAMitC,IAAQA,EAASjtC,IAE/B,CAGF,OAAKnkB,OAAOi4B,SAASg5B,IAAYjxD,OAAOi4B,SAASi5B,GAC1C,CACL,CAACD,EAAQC,GACT,CAACC,EAAQC,IAHsD,IAKnE,CM0dmBgN,CACbzjB,EACAC,EACAC,EACAC,EACAC,EACAC,GAEGsT,IAEL0J,IAAmB,EACnB9xD,EAAIm4D,UAAU/P,EAAQ,CACpBgQ,QAAS,GACTC,QAAS,GACTx0C,SAAU,MAEX,CACDquC,EACA8F,EACAvjB,EACAC,EACAC,EACAC,EACAC,EACAC,EACAgG,IAUF,MAAMwd,IAAer7D,EAAAA,EAAAA,SAAQ,KAC3B,MAAM+C,EAA8B,CAAC,EACrC,IAAKy0C,EAAc,OAAOz0C,EAC1B,IAAK,MAAMynC,KAAKgN,EAAalgB,SAAU,CACrC,MAAMxiB,EAAO01B,EAAEC,YAAY31B,KACvBA,IACF/R,EAAI+R,GAAkB,CACpB8L,IAAK,EAAG6pB,YAAYQ,WAA+B,KACnDnqB,IAAK,EAAG2pB,YAAYS,WAA+B,KACnDlqB,IAAK,EAAGypB,YAAY6vB,WAA+B,KACnDtqC,MAAO,EAAGya,YAAY6wB,mBAAuC,MAGnE,CACA,OAAOv4D,GACN,CAACy0C,KAQG+jB,GAAiBC,KAAsBrlE,EAAAA,EAAAA,WAAS,IACvDC,EAAAA,EAAAA,WAAU,KACR,IAAK6+D,GAAesG,GAAiB,OACrC,MAAMx4D,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OAEV,MAAM04D,EAAW,OAEZ14D,EAAIqhD,WAAW,sBACfrhD,EAAIqhD,WAAW,qBACfrhD,EAAIqhD,WAAW,yBAGlBoX,IAAmB,IACZ,GAET,GAAIC,IAAY,OAEhB,MAAMngE,EAAKtE,YAAY,KACjBykE,KAAYxkE,cAAcqE,IAC7B,IACH,MAAO,IAAMrE,cAAcqE,IAC1B,CAAC25D,EAAasG,GAAiB1d,IAKlC,MAAOtoB,GAAOmmC,KAAYvlE,EAAAA,EAAAA,UAA6B,MACjDwlE,IAAY3tD,EAAAA,EAAAA,aAAY,KAC5B,MAAMjL,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OAEV,MAAMlI,EAAIkI,EAAI64D,YACRh+D,EAAQ,CACZS,KAAMxD,EAAEghE,UACR19D,MAAOtD,EAAEihE,WACT19D,KAAMvD,EAAEkhE,UACR79D,MAAOrD,EAAEmhE,YAMXN,GAAUO,GACRA,GACAA,EAAM59D,OAAST,EAAMS,MACrB49D,EAAM99D,QAAUP,EAAMO,OACtB89D,EAAM79D,OAASR,EAAMQ,MACrB69D,EAAM/9D,QAAUN,EAAMM,MAClB+9D,EACAr+D,IAEL,CAACigD,KAEJznD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,GAAKhgD,GAAQkyD,EAKb,OAFA0G,KACA54D,EAAIkhD,GAAG,UAAW0X,IACX,KACL54D,EAAImhD,IAAI,UAAWyX,MAEpB,CAAC1G,EAAapX,EAAQ8d,KAGzB,MAAOO,GAASC,KAAchmE,EAAAA,EAAAA,UAAS,IAChCimE,GAASC,KAAclmE,EAAAA,EAAAA,WAAS,IAChCmmE,GAAaC,KAAkBpmE,EAAAA,EAAAA,UAAS,CAC7C23B,QAAS,GACTm/B,cAAc,EACduP,OAAO,IAEHC,GAAcn8D,KAAKY,UAAU,CACjCq0B,GACAga,EAAM+I,aACN/I,EAAM4I,eACN5I,EAAMxlB,gBACNwlB,EAAMta,SACNsa,EAAMiJ,qBAEF1gC,GAAW+hC,GAAetK,EAAO,aACvCn5C,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,IAAQkyD,EAAa,OAC1B,MAAMx5C,EAAQ,IAAM4gD,IAAW,GACzBh6C,EAAO,KACXg6C,IAAW,GACXF,GAAY5vC,GAAMA,EAAI,IAMxB,IAAImwC,EAAiC,KACrC,MAAMC,EAAkBjxD,IACjBA,EAAMkxD,UAAU92D,WAAW,SAAY4F,EAAMqtD,gBAC1B,OAApB2D,IACJA,EAAkB35C,OAAOu8B,sBAAsB,KAC7Cod,EAAkB,KAClBP,GAAY5vC,GAAMA,EAAI,OAG1BxpB,EAAIkhD,GAAG,YAAaxoC,GACpB1Y,EAAIkhD,GAAG,UAAW5hC,GAClBtf,EAAIkhD,GAAG,aAAc0Y,GACrB,MAAM5lE,EAAQgsB,OAAO/rB,YAAY,IAAMmlE,GAAY5vC,GAAMA,EAAI,GAAI,KACjE,MAAO,KACLxpB,EAAImhD,IAAI,YAAazoC,GACrB1Y,EAAImhD,IAAI,UAAW7hC,GACnBtf,EAAImhD,IAAI,aAAcyY,GACE,OAApBD,GAA0B35C,OAAOy8B,qBAAqBkd,GAC1D35C,OAAO9rB,cAAcF,KAEtB,CAACk+D,EAAapX,IAEjB,MAAMgf,GVndD,SAAuBjY,GAC5B,MAAM,OACJoH,EAAM,aACN8Q,EAAY,gBACZ/yC,EAAe,eACfouB,EAAc,MACd5iB,EAAK,IACLgyB,EAAG,QACHvxD,EAAO,aACP0xD,EAAY,qBACZqV,GAAuB,EAAK,QAC5BX,GAAU,EAAK,aACff,GACEzW,GACGoY,EAAUC,IAAe9mE,EAAAA,EAAAA,UAAsC,IAAI8I,KACpE5H,EAAU,GAAG8gD,KAAqC,QAAnBA,GAA+C,QAAnBA,EAA2B,UAAayM,EAAO3vB,UAAY,aACtHioC,EAAQ,IAAIF,EAASx5D,UAAUy5C,QAAS3iD,GAAMA,EAAE6H,SAChDg7D,EACJ9Q,KACEzH,EAAOkL,MAAQoN,EAAM5gE,OAAS,GAAK4gE,EAAM9U,MAAO9/C,KAAQA,EAAE80D,WAAW/lE,KAClEgmE,EAAUC,IAAennE,EAAAA,EAAAA,UAAS,GACnConE,EAAc,GAAGplB,KAAkBpuB,KAAmB1yB,OAAautD,EAAOkL,QAAQ95D,IAClFwnE,GAAWxvD,EAAAA,EAAAA,aACdwY,IAA2Bo+B,EAAOkL,OAASzD,MAAoB7lC,EAAM42C,WAAW/lE,IACjF,CAACutD,EAAOkL,KAAMz4D,KAETywD,EAAS2V,IAActnE,EAAAA,EAAAA,UAAS,CACrC23B,QAAS,GACTyvC,YAAa,GACbvR,OAAQ,IAAIhwD,IACZqxD,QAAS,IAAIrxD,MAET0hE,GAAkB9kC,EAAAA,EAAAA,QAAsB,MACxC+kC,GAAQ/kC,EAAAA,EAAAA,QAAO,GACfglC,GAAOhlC,EAAAA,EAAAA,QAAO,IAAI35B,KAClB4+D,GAAWjlC,EAAAA,EAAAA,QAAO,IAAI35B,KACtB6+D,GAAoBllC,EAAAA,EAAAA,SAAQ,GAC5BmlC,EAAYxoC,EApFpB,SACEA,EACA4iB,EACApuB,GAEA,MAAO,CAACwL,EAAMl3B,KAAMk3B,EAAMp3B,MAAOo3B,EAAMn3B,KAAMm3B,EAAMr3B,MAAOi6C,EAAgBpuB,GAAiBxsB,KACzF,IAEJ,CA4E4BygE,CAAczoC,EAAO4iB,EAAgBpuB,GAAmB,MAIlF3zB,EAAAA,EAAAA,WAAU,KACRqnE,EAAYQ,IAAS,CACnBnwC,QAASiwC,GAAa,GACtBR,cACAvR,OAAQmR,GAAiBc,EAAIV,cAAgBA,EAAcU,EAAIjS,OAAS,IAAIhwD,IAC5EqxD,QAAS4Q,EAAIV,cAAgBA,EAAcU,EAAI5Q,QAAU,IAAIrxD,QAE9D,CAAC+hE,EAAWR,EAAavnE,EAASomE,EAASe,IAK9C,MAAMe,GAAel+D,EAAAA,EAAAA,SAAQ,IACtBhK,GAAYu/B,GAASuyB,EAAQyV,cAAgBA,EAC3C,IAAIzV,EAAQuF,SAASjzD,OAAQJ,IAClC,MAAM6I,EAAQm6D,EAASp8D,IAAI5G,GAC3B,OACE6I,GACA6pD,GAAK7pD,EAAOs1C,EAAgBpuB,EAAiBwL,IAC7C21B,GAAgBroD,EAAMV,QAASozB,GAAO6yB,MAAMoV,KANsB,GASrE,CAACxnE,EAASu/B,EAAOuyB,EAASyV,EAAaP,EAAU7kB,EAAgBpuB,EAAiByzC,IAiLrF,OA7KApnE,EAAAA,EAAAA,WAAU,KACRunE,EAAM5hC,UACN8hC,EAAS9hC,QAAQhW,QACV,KACL43C,EAAM5hC,YAEP,CAACgiC,EAAW1C,EAAcrlE,KAE7BI,EAAAA,EAAAA,WAAU,KACR,KAAKJ,GAAYu/B,GAAUunC,GAAiBiB,GAAW,OACvD,MAAMx5C,EAAMo5C,EAAM5hC,QACZV,EAAUmxB,GAAaR,EAAQ8Q,GAC/BqB,EAAWnkE,IACf,MAAMM,EAAI0iE,EAASp8D,IAAI5G,GACvB,QAASM,GAAKoyD,GAAKpyD,EAAG69C,EAAgBpuB,EAAiBwL,IAEnD6oC,EAAa,IAAIpiE,IAAI,IACrBmhE,GAAiBrV,EAAQh6B,UAAYiwC,EAAYjW,EAAQkE,OAAS,MACnEkS,IAECG,EAAWrkE,IACfyjE,EAAYa,IACV,GAAIA,EAAIxwC,UAAYiwC,GAAaO,EAAIf,cAAgBA,GAAee,EAAItS,OAAO/vD,IAAIjC,GACjF,OAAOskE,EACT,MAAMtS,EAAS,IAAIhwD,IAAIsiE,EAAIxwC,UAAYiwC,EAAYO,EAAItS,OAAS,IAChEA,EAAOnuD,IAAI7D,GACX,MAAMqzD,EAAU,IAAIrxD,IAAIsiE,EAAIf,cAAgBA,EAAce,EAAIjR,QAAU,IAExE,OADAA,EAAQxvD,IAAI7D,GACL,CAAE8zB,QAASiwC,EAAWR,cAAavR,SAAQqB,cAGhDkR,EAAUvkE,GAAkB,GAAGuqB,KAAOvqB,IACtCwkE,EAAcxkE,IAClB,MAAMi8C,EAAU4nB,EAAS9hC,QAAQn7B,IAAI5G,GACrC,OACGmkE,EAAQnkE,KACR4jE,EAAK7hC,QAAQ9/B,IAAIsiE,EAAOvkE,OACvBi8C,GAAYA,EAAQwoB,SA1TT,GA0ToCn7D,KAAKoU,OAASu+B,EAAQyoB,UAGrEjjD,EAAQ,SAACzhB,EAAe6iC,G,IAAqB8hC,IAAQ,UAARA,OAAAA,QAAAA,IAAAA,UAAAA,KAAAA,UAAAA,GACjD,MAAM5+D,EAAMw+D,EAAOvkE,GACnB4jE,EAAK7hC,QAAQn6B,IAAI7B,EAAK,CAAE/F,QAAO6iC,aAAY8gC,MAAOp5C,IAClD+4C,EAAa/wC,GAAMA,EAAI,GACvB,MAAMpyB,EAAQmqB,GAAYuY,EAAa,oBAAsB,eAAgB,CAAE7iC,QAAOutD,QAChFuQ,EAAU,IAAM6F,EAAM5hC,UAAYxX,EAClCq6C,EAAU,KACd,MAAMH,GAAYZ,EAAS9hC,QAAQn7B,IAAI5G,IAAQykE,UAAY,GAAK,EAChEZ,EAAS9hC,QAAQn6B,IAAI5H,EAAO,CAAEykE,WAAUC,QAASp7D,KAAKoU,MAtU7C,QA0EjBnhB,eACEyD,EACA4qD,GAQA,MAAM,gBAAE76B,EAAe,eAAEouB,EAAc,MAAE5iB,EAAK,aAAE8lC,EAAY,UAAEwD,GAAcja,EAK5E,IAAI4X,EACJ,IACEA,QAAcv3C,GAAK,qBAAsB,IAAM+E,GAAchwB,EAAO+vB,GAAkB,CACpF/vB,SAEJ,CAAE,MACA,OAAO,IACT,CACA,GAAI6kE,IAAa,OAAO,KAExB,MAAMlX,EAAYuD,GAAgBsR,EAAOjnC,GAAO/6B,MAAM,EA9F5B,IA+F1B0pB,GAAY,iBAAkB,CAAElqB,QAAOklB,OAAQs9C,EAAMlgE,OAAQwiE,QAASnX,EAAUrrD,SAEhF,MAAM6F,QAAgBiH,QAAQ2hB,IAC5B48B,EAAU5kD,IAAIxM,MAAOif,IACnB,MAAMja,QAAY2wB,GAAiB1W,EAAGla,IACtC,GAAIujE,IAAa,OAAO,KACxB,IAAKtjE,EAAK,MAAM,IAAI8F,MAAM,4BAC1B,MAAM09D,EAAO1D,EAAa7lD,EAAG4D,gBACvB4lD,GAAWC,EAAAA,GAAAA,IACf9mB,EACA4mB,GAAMn+C,KAAO,KAEb,KACAm+C,GAAMj+C,KAAO,KACbi+C,GAAM/9C,KAAO,OAGfk+C,EAAAA,GAAAA,IAAiB3jE,GAAK4jE,EAAAA,GAAAA,IAAkBH,IAExCjvC,GAAax0B,EAAKwjE,GAAM/uC,OAExB,MAAMmkC,QAAe1H,GAAaj3C,EAAI2iC,GACtC,OAAI0mB,IAAoB,MACpB1K,EAAQ3kC,GAAoBj0B,EAAK44D,GAChCxkC,GAAsBp0B,GACpB,CACLD,GAAIka,EAAGla,GACP8hE,SAAU,EACPgC,kBACH7jE,MACAgxD,SAAUpU,EACVkT,UAAW71C,EAAG61C,UACdC,UAAW91C,EAAG81C,UACdC,UAAW/1C,EAAG+1C,UACdC,UAAWh2C,EAAGg2C,eAIpB,OAAIqT,IAAoB,KACjB,CAAE18D,QAASA,EAAQ/H,OAAQkO,GAAY,OAANA,GAAaykD,UAAWyP,EAClE,EA4LM6C,CAAerlE,EAAO,CACpB+vB,kBACAouB,iBACA5iB,QACA8lC,eACAwD,UAAW/G,IAEVjhE,KAAMsuB,IACL,GAAI2yC,IACF39D,EAAM,iBADR,CAIA,GAAkB,OAAdgrB,EAGF,OAFAhrB,EAAM,cACNykE,IAGFzkE,IACA0jE,EAAS9hC,QAAQ5vB,OAAOnS,GACpB2kE,GAAON,EAAQrkE,GACnBijE,EAAaqB,IACX,MAAM1gE,EAAQ,IAAIqB,IAAIq/D,GAStB,IARA1gE,EAAMuO,OAAOnS,GACb4D,EAAMgE,IAAI5H,EAAO,CACf6yD,WAAYvpD,KAAKoU,MACjB60C,SAAUpU,EACV2U,UAAW/iC,EACXwL,WACGpQ,IAEEvnB,EAAMhG,KAAOqB,KAAKkX,IAvWZ,GAuWkC67C,EAAO1vD,SACpDsB,EAAMuO,OAAOvO,EAAM8zB,OAAOjN,OAAO5hB,OACnC,OAAOjF,GArBT,IAwBDgf,MAAM,KACLziB,EAAM29D,IAAY,YAAc,SAC3BA,KAAW8G,MAEjB/hD,QAAQ,KACP+gD,EAAK7hC,QAAQ5vB,OAAOpM,GACf+3D,KAAWwF,EAAa/wC,GAAMA,EAAI,IAE7C,EAEIiyC,EAAW1B,IAAerhD,EAAMqhD,GAAc,GAC9CqB,EAAQrB,KAAkBsB,EAAWniE,IAAI6gE,IAAeuB,EAAQvB,GACpE,MAAMwC,EAAoB,IAAI1B,EAAK7hC,QAAQv4B,UAAUhI,KAClD+jE,GAAQA,EAAI5B,QAAUp5C,GAAOg7C,EAAI1iC,YAEpC,GACE0qB,EAAM,GACNG,IACC0U,GACD+B,EAAQrB,IACRgB,EAAkB/hC,UAAYwrB,EAC9B,CACA,MAAMiY,EAAWnkC,EACd7gC,MAAM,GACNJ,OACEgD,KACGghE,EAAWniE,IAAImB,IAAO+/D,IAAkBgB,EAAQ/gE,MAAS+gE,EAAQ/gE,IAAMohE,EAAWphE,KAEvF5C,MAAM,EAAG2iE,EAAgB,EAAI,GAChC,IAAK,MAAMnjE,KAASwlE,GACdrB,EAAQnkE,IAAWslE,IACrBxB,EAAkB/hC,QAAUwrB,EACxB4W,EAAQnkE,GAAQqkE,EAAQrkE,IAE1BgrB,GAAI,8BACJvJ,EAAMzhB,GAAO,IAIrB,CAIA,GACE+iE,GACAxV,EAAM,IACL6U,GACD+B,EAAQrB,KACP,IAAIc,EAAK7hC,QAAQv4B,UAAUhI,KAAMM,GAAMA,EAAE6hE,QAAUp5C,GAAOzoB,EAAE+gC,YAC7D,CACA,MAAM2iC,EAAWnkC,EACd7gC,MAAM,GACNJ,OAAQgD,IAAOghE,EAAWniE,IAAImB,IAC9B5C,MAAM,EAAG,GACTD,KAAKikE,GACJgB,GAAU/jD,EAAM+jD,GAAU,GAAM,EACtC,CACA,OAAOn6C,GAAmB,aAAc,KAAO,CAC7CkiC,MACAlsB,UACAlhC,MAAO,IAAI6iE,GAAUj6D,IAAK,I,IAAC/I,EAAOM,GAAE,E,MAAM,CACxCN,QACAylE,MAAOnlE,EAAE6H,QAAQ7F,OACjB6hE,QAASA,EAAQnkE,MAEnBwpB,SAAU,IAAIo6C,EAAK7hC,QAAQv4B,UAAUpJ,OAAQ0B,GAAMA,EAAE6hE,QAAUp5C,GAAKxhB,IAAKjH,GAAMA,EAAE9B,WAElF,CACDhE,EACAu/B,EACAunC,EACAiB,EACAh0C,EACAouB,EACAkjB,EACArP,EACAzE,EACAG,EACAqV,EACAX,EACAY,EACAK,EACAvV,EACAqV,EACAI,EACAW,KAGKl+D,EAAAA,EAAAA,SAAQ,KACb,MAAM0/D,EAAa1lE,IACjB,MAAMM,EAAI0iE,EAASp8D,IAAI5G,GACvB,QAASu7B,KAAWj7B,GAAKoyD,GAAKpyD,EAAG69C,EAAgBpuB,EAAiBwL,IAEhEunC,GAAgB4C,EAAU5C,KAAeY,EAAgB3hC,QAAU+gC,GACvE,MAAM6C,EAAejC,EAAgB3hC,QAC/BV,EAAUyhC,EAAetQ,GAAaR,EAAQ8Q,GAAgB,GAC9DsB,EAAa,IAAIpiE,IAAI,IACrBmhE,GAAiBrV,EAAQh6B,UAAYiwC,EAAYjW,EAAQkE,OAAS,MACnEkS,IAIC/hE,EAAa,IAAIH,IAAI,CACzB2jE,KACIxC,EAAgB,IAAIiB,GAAehC,EAA8C,GAApC,IAAIgC,GAAYhkE,OAAOslE,KAEpEE,EAAO,IAAI3gE,IACX4gE,EAA0C,CAAC,EAC3CC,EAAkB,IAAI9jE,IACtBqxD,EAAUvF,EAAQyV,cAAgBA,EAAczV,EAAQuF,QAAU,GACxE,IAAK,MAAMrzD,KAAS,IAAIgC,IAAI,IAAIG,KAAekxD,IAAW,CACxD,IAAKrzD,IAAUhE,EAAS,SACxB,MAAMsE,EAAI0iE,EAASp8D,IAAI5G,GACvB,IAAKM,GAAKA,EAAEiyD,WAAapU,GAAkB79C,EAAEwyD,YAAc/iC,EAAiB,SAE5E,MAAM5nB,EAAU+oD,GAAgB5wD,EAAE6H,QAASozB,GAAOn7B,OAC/CosB,GAAUrqB,EAAWF,IAAIjC,IAAUwjE,EAASh3C,IAE/Cq5C,EAAc7lE,GAASmI,EAAQY,IAAKyjB,GAAU8lC,GAAY9lC,EAAMlrB,GAAIkrB,EAAM+lC,WAC1E,IAAK,MAAM/lC,KAASrkB,EAAS,CAC3B,MAAMpC,EAAMusD,GAAY9lC,EAAMlrB,GAAIkrB,EAAM+lC,UACxCqT,EAAKh+D,IAAI7B,EAAKymB,GACVxsB,IAAU2lE,GAAcG,EAAgBjiE,IAAIkC,EAClD,CACF,CACA,MAAMggE,EAAc/T,EAAO5xD,OAAQgD,GAAMpH,GAAWmG,EAAWF,IAAImB,IAAMsiE,EAAUtiE,IAC7E47C,EAAgB,IAAI4kB,EAAK7hC,QAAQv4B,UACpCpJ,OAAQ0B,GAAMA,EAAE6hE,QAAUA,EAAM5hC,SAChCh5B,IAAKjH,GAAMA,EAAE9B,OACVi8C,EAAU6mB,EAAee,EAAS9hC,QAAQn7B,IAAIk8D,QAAgB/4D,EACpE,MAAO,CACL07D,MAAO,IAAIG,EAAKp8D,UAChBs8D,kBACAC,cACAF,gBACA7mB,gBACAgnB,oBACEhqE,GACAqlC,EACG7gC,MAAM,GACNgB,KACE4B,KACGsiE,EAAUtiE,KAAOjB,EAAWF,IAAImB,MACjCygE,EAAS9hC,QAAQn7B,IAAIxD,IAAIqhE,UAAY,GAzf/B,GA2ffpiD,KAAM45B,EAAU,mCAAqC,OAEtD,CACD+mB,EACAF,EACAvnC,EACA4iB,EACApuB,EACAiiC,EACAh2D,EACAomE,EACAiB,EACAvV,EACAqV,EACAI,EACAW,EACAV,EACAO,GAEJ,CUuJqBkC,CAAc,CAC/BjU,OAAQzc,EAAMgJ,eACdukB,aAAcvtB,EAAM+I,aACpBvuB,gBAAiBwlB,EAAMxlB,gBACvBouB,eAAgB5I,EAAM4I,eACtB5iB,SACAgyB,IAAK2U,GACLxU,aAAc4U,GAAYxuC,UAAY2uC,IAAeH,GAAYE,QAAUJ,GAC3EW,qBACET,GAAYxuC,UAAY2uC,IAAeH,GAAYrP,eAAiBmP,GACtEA,WACApmE,QAAS6jD,GAAetK,EAAO,YAC/B8rB,gBACApmC,SAAUsa,EAAMta,SAChB66B,KAAMvgB,EAAMiJ,qBAER0nB,IAAelgE,EAAAA,EAAAA,SACnB,IACE68D,GAAW4C,MAAMrlE,OAAQ+lE,GAAMtD,GAAWiD,gBAAgB7jE,IAAIqwD,GAAY6T,EAAE7kE,GAAI6kE,EAAE5T,YACpF,CAACsQ,KAKGuD,IAAmBxnC,EAAAA,EAAAA,QAAOikC,IAChCuD,GAAiBrkC,QAAU8gC,IAC3BzmE,EAAAA,EAAAA,WAAU,KACR,IAAKytB,KAAsBoxC,EAAa,OACxC,MAAMlyD,EAAM86C,EAAO9hB,SAASgnB,SAC5B,IAAKhgD,EAAK,OACV,MAAM/I,EAAQu1C,EAAM+I,aACdn+C,EAAQmqB,GAAY,oCAAqC,CAC7DtqB,QACAJ,WAAY21C,EAAM4I,eAClBpuB,gBAAiBwlB,EAAMxlB,gBACvBzrB,KAAMyE,EAAIuzD,YAEZpyC,GAAY,gBAAiB,CAAElqB,WAAUu7B,KACzC,MAAM8qC,EAAO,KACX,MAAMC,EAAUF,GAAiBrkC,QAC3Bp+B,EAAU2iE,EAAQb,MAAM18D,IAAK8pB,IACjC,MAAMgK,EAAUy1B,GAAYz/B,EAAKvxB,GAAIuxB,EAAK0/B,UACpCjxD,EAAK,OAAOu7B,IAClB,IAAIp6B,GAAU,EACd,MAAMN,IAAe4G,EAAI8yD,UAAUv6D,GACnC,IACEmB,EAAUN,GAAc4G,EAAIg2D,eAAez9D,EAC7C,CAAE,MAEF,CACA,MAAO,CAAEA,KAAIqvD,OAAQ2V,EAAQR,gBAAgB7jE,IAAI46B,GAAU16B,aAAYM,aAEnE8jE,IAAqBvmE,GAASsmE,EAAQP,YAAY36D,SAASpL,GAC3DwmE,EAAgBD,GAAoB5iE,EAAQyqD,MAAOvtD,IAAOA,EAAE8vD,QAAU9vD,EAAE4B,SAC9E,MAAO,CACLzC,QACAumE,mBACAC,gBACAC,gBAAiBH,EAAQP,YAAYzjE,OACrCokE,cAAenxB,EAAMgJ,eAAej8C,OACpC48C,iBAAkBonB,EAAQN,qBAAuBriE,EAAQnC,KAAMX,IAAOA,EAAE4B,SACxEkkE,eAAgB59D,EAAI69D,gBACpBC,kBAAmB99D,EAAIizD,iBACvBr4D,YAGEmjE,EAAWz7C,GAAmB,QAASg7C,GAC7C,IAAIU,EAAgB,GAChBC,GAAe,EACnB,MAAMC,EAAa,KACjB,MAAM,QAAEtjE,KAAY2D,GAAW++D,IACzBa,EAAS,IACV5/D,EACH3D,QAASA,EAAQrB,OACjB6kE,eAAgBxjE,EAAQvD,OAAQS,GAAMA,EAAE8vD,QAAQruD,OAChD8kE,iBAAkBzjE,EAAQvD,OAAQS,IAAOA,EAAE4B,SAASH,QAEhDu6B,EAAUv2B,KAAKY,UAAUggE,GAC3BrqC,IAAYkqC,IACd78C,GAAY,eAAgBg9C,GAC5BH,EAAgBlqC,IAGdwqC,EAAW,MACVL,GAAgBX,IAAOG,gBAC1BQ,GAAe,EACf7mE,MAGEmnE,EAAU,IAAMp9C,GAAY,eAClCnhB,EAAIkhD,GAAG,SAAUod,GACjBt+D,EAAIkhD,GAAG,QAASqd,GAChBL,IACA,MAAMlqE,EAAQgsB,OAAO/rB,YAAYiqE,EAAY,KAC7C,MAAO,KACL9mE,EAAM,cACN2mE,IACA/9C,OAAO9rB,cAAcF,GACrBgM,EAAImhD,IAAI,SAAUmd,GAClBt+D,EAAImhD,IAAI,QAASod,KAElB,CACDrM,EACApX,EACAtoB,GACAga,EAAM+I,aACN/I,EAAM4I,eACN5I,EAAMxlB,gBACNwlB,EAAMta,SACNsa,EAAMiJ,mBACNjJ,EAAMgJ,kBAIRgpB,EAAAA,EAAAA,iBAAgB,KACd,MAAMzP,EAAa+K,GAAW4C,MAC3BrlE,OAAQ+lE,GAAMtD,GAAWiD,gBAAgB7jE,IAAIqwD,GAAY6T,EAAE7kE,GAAI6kE,EAAE5T,YACjExpD,IAAKo9D,GAAMA,EAAE5kE,KACVshC,EAAaggC,GAAW4C,MAC3BrlE,OAAQ+lE,IAAOtD,GAAWiD,gBAAgB7jE,IAAIqwD,GAAY6T,EAAE7kE,GAAI6kE,EAAE5T,YAClExpD,IAAKo9D,GAAMA,EAAE5kE,KAGhBq2D,GAAUK,QAAQH,EAAYj1B,EAAY,CACxCk1B,iBAAkBqK,IAAWE,GAAYxuC,UAAY2uC,IAAeH,GAAYrP,gBAEjF,CAAC4P,GAAYT,GAASE,GAAaG,MACtCrmE,EAAAA,EAAAA,WAAU,IAAM,IAAMw7D,GAAUK,QAAQ,GAAI,IAAK,IAEjD,MAAMuP,IAAqB5oC,EAAAA,EAAAA,QAAO,KAClCxiC,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SACtB0e,EAAO,IAAIv3D,WAAWjL,IAC1B49D,GAAW4C,MAAM18D,IAAKo9D,GAAM,CAAC7T,GAAY6T,EAAE7kE,GAAI6kE,EAAE5T,UAAW4T,EAAE5kE,OAE1DmmE,EAAe,IAAI1lE,IAAI82D,GAAUtvD,UASjClC,EIh0BH,SACLg/D,EACAtmE,EACA2nE,EACAC,EACA5rE,GAEA,IAAKA,IAAYgE,EACf,MAAO,CACL++C,cAAe,GACfC,cAAe,GACfC,mBAAmB,EACnBC,kBAAkB,EAClB78B,KAAM,MAEV,MAAMwlD,EAAYzkE,GAAckjE,EAAQT,cAAcziE,IAAM,GACtDwhE,EAAWxhE,GAAcykE,EAASzkE,GAAG5B,KAAKomE,GAC1C7oB,EAAgBunB,EAAQP,YAAY3lE,OACvCgD,IAAOwhE,EAAQxhE,IAAMykE,EAASzkE,GAAGgrD,MAAMuZ,IAEpC3oB,EAAgB,IACjB,IAAIh9C,IAAI,IACLskE,EAAQtnB,eAAiB,MAC1BsnB,EAAQP,YAAY3lE,OAAQgD,IAAOwhE,EAAQxhE,KAAO27C,EAAc3zC,SAAShI,OAG1Eif,EACJikD,EAAQjkD,OAASuiD,EAAQ5kE,GAAS,4CAA8C,MAClF,MAAO,CACL++C,gBACAC,gBACAC,mBAAoB58B,IAAS08B,EAAc3zC,SAASpL,GACpDk/C,iBAAkBonB,EAAQN,qBAAuBhnB,EAAcx9C,KAAM4B,GAAMA,IAAMpD,GACjFqiB,OAEJ,CJ6xBmBylD,CACbjF,GACAttB,EAAM+I,aAVazhB,IACnB,MAAMv7B,EAAK,OAAOu7B,IAClB,IACE,QAAS9zB,GAAK8yD,UAAUv6D,IAAOyH,EAAIg2D,eAAez9D,EACpD,CAAE,MACA,OAAO,CACT,GAMCu7B,GAAY6qC,EAAazlE,IAAIwlE,EAAK7gE,IAAIi2B,IAAY,IACnD/e,IAEIm1C,IAAiB1d,EAAM+I,cAAgBh3C,EAAOy3C,cAAc3zC,SAASmqC,EAAM+I,cAG3EkkB,EACJvP,IAAiB4P,GAAWkD,YAAYvkE,KAAM4B,GAAMkE,EAAO03C,cAAc5zC,SAAShI,IACpFm/D,GAAgB0B,GACdA,EAAInwC,UAAY2uC,IAAewB,EAAIhR,eAAiBA,GAAgBgR,EAAIzB,QAAUA,EAC9EyB,EACA,CAAEnwC,QAAS2uC,GAAaxP,eAAcuP,UAE5C,MAAMuF,EAAezhE,KAAKY,UAAUI,GAChCkgE,GAAmBzlC,UAAYgmC,IACnCP,GAAmBzlC,QAAUgmC,EAC7B79C,GAAY,qBAAsB,CAChCznB,QAAS6E,EAAOy3C,cAAcz8C,OAC9B28C,kBAAmB33C,EAAO23C,kBAC1BC,iBAAkB53C,EAAO43C,iBACzB78B,KAAM/a,EAAO+a,OAEfq9B,EAAS,CAAEn6C,KAAM,yBAA0B+B,MAC1C,CAACo4C,EAAUmE,EAAQgf,GAAYX,GAAS3sB,EAAM+I,aAAcmkB,GAAa3kD,MAI5E1hB,EAAAA,EAAAA,WAAU,KACR,GAAI6+D,EAAa,OAEjB,MAAM+M,EAAa,KACjB,MAAMj/D,EAAM86C,EAAO9hB,SAASgnB,SAC5B,QAAKhgD,KACDA,EAAI69D,kBACN1L,GAAe,IACR,IAKX,GAAI8M,IAAc,OAGlB,MAAMtX,EAAW1zD,YAAY,KACvBgrE,KAAc/qE,cAAcyzD,IAC/B,KACGuX,EAAU34D,WAAW,KACzBrS,cAAcyzD,GACdwK,GAAe,IACd,KAEH,MAAO,KACLj+D,cAAcyzD,GACdwX,aAAaD,KAEd,CAAChN,EAAapX,KAIjBznD,EAAAA,EAAAA,WAAU,KACR,IAAKyjD,GAAetK,EAAO,QAAiC,IAAvBqO,EAAYthD,OAE/C,YADAy6D,EAAc,IAGhB,IAAIr2D,GAAY,EAyChB,MAvCC,WACC,MAAM6rD,EAAWhd,EAAM4I,eACjBntB,SACE5hB,QAAQ2hB,IACZ6yB,EAAY76C,IAAIxM,MAAOif,IACrB,MAAMja,QAAY2wB,GAAiB1W,EAAGla,IACtC,IAAKC,EAAK,OAAO,KACjB,MAAMwjE,EAAO1D,GAAa7lD,EAAG4D,gBACvB4lD,GAAWC,EAAAA,GAAAA,IACf1S,EACAwS,GAAMn+C,IAEN,KACAm+C,GAAMj+C,IACNi+C,GAAM/9C,IACNuuB,EAAMta,WAERiqC,EAAAA,GAAAA,IAAiB3jE,GAAK4jE,EAAAA,GAAAA,IAAkBH,IACxCjvC,GAAax0B,EAAKwjE,GAAM/uC,OAIxB,MAAMmkC,QAAe1H,GAAaj3C,EAAI+2C,GACtC,OAAI7rD,EAAkB,MAClByzD,EAAQ3kC,GAAoBj0B,EAAK44D,GAChCxkC,GAAsBp0B,GACpB,CACLD,GAAIka,EAAGla,GACPC,MACAgxD,WACA6Q,SAAU,EACPgC,wBAIThlE,OAAQ5D,GAA8B,OAANA,GAC7BkK,GAAWq2D,EAAc/rC,EAC/B,EArCA,GAuCM,KACLtqB,GAAY,IAEb,CACDk9C,EACA/D,GAAetK,EAAO,OACtBA,EAAM4I,eACN5I,EAAMta,SACNomC,MAIFjlE,EAAAA,EAAAA,WAAU,KACR,GAAIm5C,EAAMiJ,oBAAsBhB,EAAc,CAC5C,MAAMsI,EAAc,CAACtI,GACjBK,GAAeiI,EAAYpkD,KAAKm8C,GACpC2X,GAAY,IAAIhB,MAAiB1O,GACnC,MACE0P,GAAY,MAEd,MAAO,IAAMA,GAAY,OACxB,CAACjgB,EAAMiJ,mBAAoBhB,EAAcK,KAG5CzhD,EAAAA,EAAAA,WAAU,KACR,MAAM2M,EAAM86C,EAAO9hB,SAASgnB,SAC5B,GAAKhgD,GAAQkyD,GAAgBlyD,EAAI69D,gBAEjC,GAAIrxB,EAAMkJ,MAAQynB,GAAa5jE,OAAS,EAAG,CAEzC,MAAM6lE,EAAS,SAASjC,GAAa,GAAG3kE,UACnCwH,EAAI8yD,UAAU,gBACjB9yD,EAAIq/D,UAAU,cAAe,CAC3B7iE,KAAM,aACNhE,IAAK4mE,EACLziE,SAAU,MAGdqD,EAAIs/D,WAAW,CAAExiE,OAAQ,cAAeyiE,aAAc,GACxD,MACEv/D,EAAIs/D,WAAW,MACXt/D,EAAI8yD,UAAU,gBAChB9yD,EAAIw/D,aAAa,gBAGpB,CAAChzB,EAAMkJ,KAAMynB,GAAcjL,EAAapX,IAE3C,MAAMgG,IAAc71C,EAAAA,EAAAA,aACjB1F,IAGC,IAAIqyD,IAGCryD,EAAEk6D,eAAexjB,QAA2B6b,UAAU,gBAA3D,CAEA,GAAIH,EAAe,CACjB,MAAM5gE,EAAQwO,EAAE02C,OAAOyjB,iBACjBC,EAAY5oE,GAAO6oE,wBAGnBC,EAAO9oE,GAAO+oE,cACjBC,cAAc,mDACbH,wBACJrJ,EAAS,CACPn8C,KAAM,CAAEwa,IAAKrvB,EAAEy7C,OAAOpsB,IAAK3W,IAAK1Y,EAAEy7C,OAAO/iC,IAAK1iB,KAAMgK,EAAE02C,OAAOsX,WAC7DtR,MAAOmM,GACL7oD,EAAEw7C,OAAS,CAAErzC,EAAG,EAAGC,EAAG,GACtB,CAAE4jB,QAASx6B,GAAOipE,aAAe,EAAGxuC,OAAQz6B,GAAO0kD,cAAgB,GACnEokB,GAAQF,EACJ,CACEpR,MAAOsR,EAAK9lC,KAAO4lC,EAAU5lC,KAC7B00B,OAAQoR,EAAKz8B,MAAQu8B,EAAU5lC,KAC/BmtB,MAAO2Y,EAAK18B,IAAMw8B,EAAUx8B,IAC5BurB,MAAOmR,EAAKx8B,OAASs8B,EAAUx8B,KAEjC,OAGV,CAKA8uB,EAAchM,GAAe1gD,EAAEy7C,OAAOpsB,IAAKrvB,EAAEy7C,OAAO/iC,IAAK,CAAEw2B,kBAAiB+hB,IA9BM,GAgCpF,CAACvE,EAAe2F,EAAQD,EAAeljB,EAAc+hB,IAMjDyJ,IAAiBhjE,EAAAA,EAAAA,SAA0C,KAC/D,IAAK65C,GAAetK,EAAO,QAAiC,IAAvBqO,EAAYthD,OAAc,OAAO,KACtE,MAAMg7B,EAA8B,GACpC,IAAK,MAAM9hB,KAAMooC,EAAa,CAC5B,MAAM,UAAEyN,EAAS,UAAEC,EAAS,UAAEC,EAAS,UAAEC,GAAch2C,EACvD,GAAiB,MAAb61C,GAAkC,MAAbC,GAAkC,MAAbC,GAAkC,MAAbC,EACjE,SAEF,MAAMyX,EAAKlY,GAAiBM,EAAWC,GACjC4X,EAAKnY,GAAiBQ,EAAWD,GACjC6X,EAAKpY,GAAiBQ,EAAWC,GACjC5sB,EAAKmsB,GAAiBM,EAAWG,GACvCl0B,EAAS57B,KAAK,CACZ6D,KAAM,UACNkrC,WAAY,CAAEnvC,GAAIka,EAAGla,GAAIwZ,KAAMU,EAAG4D,gBAClCoe,SAAU,CACRj4B,KAAM,UACNsQ,YAAa,CAAC,CAACozD,EAAIC,EAAIC,EAAIvkC,EAAIqkC,MAGrC,CACA,MAAO,CAAE1jE,KAAM,oBAAqB+3B,aACnC,CAACsmB,EAAa/D,GAAetK,EAAO,SAEvC,OACE,UAACz8B,MAAAA,CAAI3a,UAAU,gB,WACX88D,IAAgB2B,IAChB,SAAC9jD,MAAAA,CAAI3a,UAAU,2F,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,gC,SAAgC,6BAGhDy+D,IACC,SAAC9jD,MAAAA,CAAI3a,UAAU,2F,UACb,SAAC6X,IAAAA,CAAE7X,UAAU,2B,SAA4By+D,OAI7C,UAAC33D,GAAAA,GAAGA,CACFuuB,IAAKqwB,EACLulB,iBAAkBxO,GAClBkG,SAAUA,EACVuI,UAAU,EACVC,WAAS,EACT1gE,MAAO,CAAE0M,MAAO,OAAQC,OAAQ,QAChCg0D,oBACEh0B,EAAM3vC,OAAO43C,eAAiBjI,EAAMoJ,aAAaC,QAAU,CAAC,kBAAoB,GAElF7iC,QAAS8tC,GACT2f,OAAQ,IAAMtO,GAAe,GAC7BuO,QAAU/3D,IAER,MAAMkxD,EAAW,EAA4CA,SACzDA,GAAU92D,WAAW,aAEvBkf,GAAI,iBAGoB,eAAtBtZ,EAAMzJ,OAAOU,MAAyBi6D,GAAU92D,WAAW,UAC/DovD,GAAe,GACf2B,EAAYnrD,EAAMzJ,OAAOqM,SAAW,oC,WAKtC,SAACm2C,GAAAA,GAAKA,CAACnpD,GAAG,qBAAqBiE,KAAK,aAAamlD,MAAO,CAAE,qBAAsB,MAWhF,SAACD,GAAAA,GAAKA,CAACnpD,GAAG,oBAAoBiE,KAAK,aAAamlD,MAAO,CAAE,qBAAsB,MAI/E,SAACD,GAAAA,GAAKA,CAACnpD,GAAG,mBAAmBiE,KAAK,aAAamlD,MAAO,CAAE,qBAAsB,KAE7EuQ,GAAesG,IAAmBhsB,EAAM3vC,OAAOm4C,WAC9C,SAACuP,GAAaA,CACZrxD,QAASyiD,EACT19C,KAAM69D,EACNxhE,QAASuhE,EACTrR,IAAK2U,GACL1U,UAAWsR,EACXrR,SAAS,qBAETC,cAAe0U,KAAYS,GAAWmD,sBAOzC/K,GACCsG,IACA1hB,GAAetK,EAAO,aACtBstB,GAAW4C,MAAM18D,IAAK,I,IAAA,GAAEzH,EAAE,IAAEC,EAAG,SAAEgxD,EAAQ,SAAE6Q,GAAU,E,OACnD,SAAC5Y,GAAAA,GAAMA,CAELlpD,GAAI,OAAOA,KAAMixD,IACjBhtD,KAAK,SACLhE,IAAKq0D,GAAar0D,EAAK,CACrBkE,QACE29D,IACE,GAAG7Q,KAAyB,QAAbA,GAAmC,QAAbA,EAAqB,UAAYhd,EAAMta,YAEhFs3B,WACAt3B,SAAUsa,EAAMta,SAChB66B,KAAMvgB,EAAMiJ,mBACZzgC,KAAK,IAEPrY,SAAU,IAAM0jB,G,UAEhB,SAACqhC,GAAAA,GAAKA,CACJnpD,GAAI,aAAaA,KAAMixD,IACvBhtD,KAAK,SACLmlD,MAAO,CACL,4BAA6B,CAAE99B,SAAU,EAAG0hC,MAAO,GACnD,iBAAkBuU,GAAWiD,gBAAgB7jE,IAAIqwD,GAAYhxD,EAAIixD,IAC7D,EACA,GAEN9E,SAAS,uBAxBN,GAAGnsD,KAAMixD,KAAYhd,EAAMta,cA+BrCggC,GACCsG,IACAhsB,EAAM3vC,OAAOo4C,QACb6B,GAAetK,EAAO,aACtBstB,GAAW4C,MAAM18D,IAAK,I,IAAA,GAAEzH,EAAE,IAAEC,EAAG,SAAEgxD,GAAU,E,OACzC,SAAC/H,GAAAA,GAAMA,CAELlpD,GAAI,UAAUA,IACdiE,KAAK,SACLhE,IAAK,SAASA,YAAcg0C,EAAMiJ,mBAAqB,QAAU,KACjE94C,SAAU,IAAM0jB,G,UAEhB,SAACqhC,GAAAA,GAAKA,CACJnpD,GAAI,gBAAgBA,IACpBiE,KAAK,SACLmlD,MAAO,CACL,4BAA6B,CAAE99B,SAAU,EAAG0hC,MAAO,GACnD,iBAAkBuU,GAAWiD,gBAAgB7jE,IAAIqwD,GAAYhxD,EAAIixD,IAC7D,EACA,GAEN9E,SAAS,sBAfN,UAAUnsD,OAuBpB25D,GACCsG,IACA1hB,GAAetK,EAAO,QACtBunB,EAAW/zD,IAAK,I,IAAA,GAAEzH,EAAE,IAAEC,EAAG,SAAEgxD,EAAQ,SAAE6Q,GAAU,E,OAC7C,SAAC5Y,GAAAA,GAAMA,CAELlpD,GAAI,WAAWA,KAAMixD,IACrBhtD,KAAK,SACLhE,IAAKq0D,GAAar0D,EAAK,CACrBgxD,WACAt3B,SAAUsa,EAAMta,SAChB66B,KAAMvgB,EAAMiJ,mBACZzgC,KAAK,EACLtY,QACE29D,IACE,GAAG7Q,KAAyB,QAAbA,GAAmC,QAAbA,EAAqB,UAAYhd,EAAMta,cAGlFv1B,SAAU,I,UAEV,SAAC+kD,GAAAA,GAAKA,CACJnpD,GAAI,iBAAiBA,KAAMixD,IAC3BhtD,KAAK,SACLkoD,SAAS,uBAlBN,OAAOnsD,KAAMixD,KAAYhd,EAAMta,cAwBzCsa,EAAM3vC,OAAO43C,cAAgBA,IAC5B,UAACgN,GAAAA,GAAMA,CAAClpD,GAAG,eAAeiE,KAAK,UAAU0I,KAAMuvC,E,WAC7C,SAACiN,GAAAA,GAAKA,CACJnpD,GAAG,iBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAc,cACd,eAAgB,OAGpB,SAACD,GAAAA,GAAKA,CACJnpD,GAAG,oBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,EACd,eAAgB,OAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,mBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,QACtB,YAAa,GACb,cAAe,SACf,sBAAsB,GAExB3C,MAAO,CACL,aAAcwL,GACd,kBAAmB,UACnB,kBAAmB,UAO1B3gB,EAAM3vC,OAAO63C,UAAYA,IACxB,UAAC+M,GAAAA,GAAMA,CAAClpD,GAAG,WAAWiE,KAAK,UAAU0I,KAAMwvC,E,WACzC,SAACgN,GAAAA,GAAKA,CACJnpD,GAAG,gBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,eAAgB,QAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,mBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,IACd,eAAgB,SAOvB3gB,EAAM3vC,OAAO83C,aAAeA,IAC3B,UAAC8M,GAAAA,GAAMA,CAAClpD,GAAG,cAAciE,KAAK,UAAU0I,KAAMyvC,E,WAC5C,SAAC+M,GAAAA,GAAKA,CACJnpD,GAAG,mBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,eAAgB,OAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,sBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,EACd,iBAAkB,CAAC,EAAG,GACtB,eAAgB,SAUvB3gB,EAAM3vC,OAAO+3C,gBAAkBA,IAC9B,UAAC6M,GAAAA,GAAMA,CAAClpD,GAAG,iBAAiBiE,KAAK,UAAU0I,KAAM0vC,E,WAC/C,SAAC8M,GAAAA,GAAKA,CACJnpD,GAAG,yBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,EACd,iBAAkB,CAAC,EAAG,GACtB,eAAgB,OAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,QACtB,YAAa,GACb,cAAe,SACf,sBAAsB,GAExB3C,MAAO,CACL,aAAcwL,GACd,kBAAmB,UACnB,kBAAmB,QAQ1B3gB,EAAM3vC,OAAOg4C,QAAUA,IACtB,UAAC4M,GAAAA,GAAMA,CAAClpD,GAAG,SAASiE,KAAK,UAAU0I,KAAM2vC,E,WACvC,SAAC6M,GAAAA,GAAKA,CACJnpD,GAAG,cACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,eAAgB,QAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,iBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,IACd,iBAAkB,CAAC,EAAG,GACtB,eAAgB,SAOvB3gB,EAAM3vC,OAAOi4C,eAAiBA,IAC7B,UAAC2M,GAAAA,GAAMA,CAAClpD,GAAG,gBAAgBiE,KAAK,UAAU0I,KAAM4vC,E,WAC9C,SAAC4M,GAAAA,GAAKA,CACJnpD,GAAG,qBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,eAAgB,QAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAcwL,GACd,aAAc,EACd,eAAgB,OAGpB,SAACzL,GAAAA,GAAKA,CACJnpD,GAAG,uBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,QACtB,YAAa,GACb,cAAe,SACf,sBAAsB,GAExB3C,MAAO,CACL,aAAcwL,GACd,kBAAmB,UACnB,kBAAmB,UAW1BrW,GAAetK,EAAO,QAAUyzB,KAC/B,UAACxe,GAAAA,GAAMA,CAAClpD,GAAG,WAAWiE,KAAK,UAAU0I,KAAM+6D,G,WACzC,SAACve,GAAAA,GAAKA,CACJnpD,GAAG,gBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAczsC,GACd,eAAgB,QAGpB,SAACwsC,GAAAA,GAAKA,CACJnpD,GAAG,gBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAczsC,GACd,aAAc,GACd,YAAa,GACb,eAAgB,OAGpB,SAACwsC,GAAAA,GAAKA,CACJnpD,GAAG,mBACHiE,KAAK,OACLmlD,MAAO,CACL,aAAczsC,GACd,aAAc,EACd,iBAAkB,CAAC,EAAG,GACtB,eAAgB,QAGpB,SAACwsC,GAAAA,GAAKA,CACJnpD,GAAG,iBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,iBACd,YAAa,GACb,mBAAoB,OACpB,qBAAqB,EACrB,sBAAsB,GAExB3C,MAAO,CACL,aAAczsC,GACd,kBAAmB,UACnB,kBAAmB,UAK1Bs3B,EAAM3vC,OAAOk4C,mBAAqBA,IACjC,UAAC0M,GAAAA,GAAMA,CAAClpD,GAAG,oBAAoBiE,KAAK,UAAU0I,KAAM6vC,E,WAMlD,SAAC2M,GAAAA,GAAKA,CACJnpD,GAAG,2BACHiE,KAAK,OACLnF,OAAQ,CAAC,KAAM,CAAC,MAAO,aAAa,GACpCitD,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C3C,MAAOoQ,MAET,SAACrQ,GAAAA,GAAKA,CACJnpD,GAAG,6BACHiE,KAAK,OACLnF,OAAQ,CAAC,KAAM,CAAC,MAAO,aAAa,GACpCitD,OAAQ,CAAE,WAAY,OAAQ,YAAa,SAC3C3C,MAAO,IAAKoQ,GAAwB,iBAAkB/D,SAY3DxhB,EAAM3vC,OAAO23C,iBAAmBihB,EAAiBlhC,SAASh7B,OAAS,IAClE,UAACkoD,GAAAA,GAAMA,CAAClpD,GAAG,kBAAkBiE,KAAK,UAAU0I,KAAMuwD,E,WAChD,SAAC/T,GAAAA,GAAKA,CACJnpD,GAAG,uBACHiE,KAAK,OACL8nD,OAAQ,CAAE,YAAa,QAAS,WAAY,SAC5C3C,MAAO,CACL,aAAc8F,GACd,af92CP,CAAC,OAAQ,CAAC,MAAO,QAAS,IAAK,Qei3C5B,SAAC/F,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,SACtB,YAAa,GACb,eAAgB,EAChB,mBAAoB,OACpB,iBAAkB,IAClB,sBAAsB,EACtB,yBAAyB,EACzB,kBAAmB,CAAC,MAAO,YAE7B3C,MAAO,CACL,afl4CmB,Uem4CnB,kBAAmB,UACnB,kBAAmB,UAc1BnV,EAAM3vC,OAAO03C,eAAiBghB,EAAqBhhC,SAASh7B,OAAS,IACpE,SAACkoD,GAAAA,GAAMA,CAAClpD,GAAG,gBAAgBiE,KAAK,UAAU0I,KAAMqwD,E,UAC9C,SAAC7T,GAAAA,GAAKA,CACJnpD,GAAG,sBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,SACtB,YAAa,GACb,eAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,0BAA2B,WAC3B,uBAAwB,WACxB,kBAAmB,CAAC,MAAO,YAE7B3C,MAAO,CACL,ajBh7CP,CAAC,OAAQ,CAAC,MAAO,SAJc,UACA,WiBo7CxB,kBAAmB,UACnB,kBAAmB,SAQ1BnV,EAAM3vC,OAAOy3C,iBAAmB6gB,EAAiB5gC,SAASh7B,OAAS,IAClE,UAACkoD,GAAAA,GAAMA,CAAClpD,GAAG,mBAAmBiE,KAAK,UAAU0I,KAAMiwD,E,WACjD,SAACzT,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACLmlD,MAAO,CACL,gBAAiB,EACjB,eAAgBuM,KAChB,iBAAkB,QAGtB,SAACxM,GAAAA,GAAKA,CACJnpD,GAAG,wBACHiE,KAAK,SACLmlD,MAAO,CACL,gBAAiB,EACjB,eAAgBuM,KAChB,sBAAuB,UACvB,sBAAuB,QAG3B,SAACxM,GAAAA,GAAKA,CACJnpD,GAAG,yBACHiE,KAAK,SACL8nD,OAAQ,CACN,aAAc,CAAC,MAAO,SACtB,YAAa,GACb,cAAe,CAAC,EAAG,KACnB,cAAe,MACf,sBAAsB,GAExB3C,MAAO,CACL,aAAc,UACd,kBAAmB,UACnB,kBAAmB,UAK1B2U,IAIC,SAACqK,GAAAA,GAAMA,CAELxoB,UAAWme,EAAMl8C,KAAKwa,IACtBsjB,SAAUoe,EAAMl8C,KAAK6D,IACrB3G,OAAQg/C,EAAMrU,MACd1iC,QDxiDgB0iC,GCwiDIqU,EAAMrU,MDtiD7B,CADGA,GAAM9+C,SAAS,UAdR,EAciC8+C,GAAM9+C,SAAS,UAdhD,EAcyE,EAC/E8+C,GAAMl/C,WAAW,UAfX,O,UCujDP,SAAC++C,GAAWA,CACVC,QAAS0U,EACTrhB,eAAgB5I,EAAM4I,eACtB4M,QAAS,IAAMuU,EAAS,MACxBtU,MAAOqU,EAAMrU,SAVVqU,EAAMrU,QAef,SAACxB,GAAgBA,CAAC3F,OAAQA,KAE1B,SAACkI,GAAUA,CAAClI,OAAQA,KAEpB,SAAC8lB,GAAAA,GAAiBA,CAACplD,SAAS,eAAeqlD,UAAU,EAAOC,gBAAc,KAC1E,SAACC,GAAAA,GAAYA,CAACvlD,SAAS,eAAeqnB,KAAK,SAASj0B,SAAU,YDxjD/D,IAAqBqzC,EC4jD5B,C,2DK7lDA,SAAS+e,GAAO,G,IAAA,UACd5rE,EAAS,aACT6rE,EAAY,MACZnhE,EAAK,IACLkJ,EAAM,EAAC,IACPoE,EAAM,OACH9X,GANW,EAQd,MAAM4rE,EAAU9hC,EAAAA,QACd,IAAOt9B,MAAMC,QAAQjC,GAASA,EAAQgC,MAAMC,QAAQk/D,GAAgBA,EAAe,CAACj4D,EAAKoE,GACzF,CAACtN,EAAOmhE,EAAcj4D,EAAKoE,IAG7B,OACE,UAAC+zD,GAAAA,GAAoB,CACnB1rE,YAAU,SACVwrE,aAAcA,EACdnhE,MAAOA,EACPkJ,IAAKA,EACLoE,IAAKA,EACLhY,WAAWQ,EAAAA,EAAAA,IACT,sOACAR,MAEEE,E,WAEJ,SAAC6rE,GAAAA,GAAqB,CACpB1rE,YAAU,eACVL,WAAWQ,EAAAA,EAAAA,IACT,qM,UAGF,SAACurE,GAAAA,GAAqB,CACpB1rE,YAAU,eACVL,WAAWQ,EAAAA,EAAAA,IACT,mGAILkM,MAAM43B,KAAK,CAAEngC,OAAQ2nE,EAAQ3nE,QAAU,CAACogC,EAAG3K,KAC1C,SAACmyC,GAAAA,GAAqB,CACpB1rE,YAAU,eAEVL,UAAU,0OADL45B,MAMf,CChCA,MAEMoyC,GAAoB,CACxB,CAAEthE,MAAO,IAAKwS,MAAO,SACrB,CAAExS,MAAO,IAAKwS,MAAO,SACrB,CAAExS,MAAO,IAAKwS,MAAO,SACrB,CAAExS,MAAO,IAAKwS,MAAO,SACrB,CAAExS,MAAO,KAAMwS,MAAO,UACtB,CAAExS,MAAO,IAAKwS,MAAO,UAIvB,SAAS+uD,GAAQxoE,EAAWQ,GAC1B,OAAOA,EAAS,EAAIR,GAAKQ,EAAS,GAAK,CACzC,CAyBO,SAASioE,GACdrY,EACApqB,EACA0iC,GAEA,MAAMloE,EAAS4vD,EAAO1vD,OAChBY,EAAQtB,GAAcowD,EAAOpwD,GAAGpB,MAAM,EAAG,GACzC+pE,EAAa3oE,GAAwC,OAA1BowD,EAAOpwD,GAAGpB,MAAM,EAAG,GAG9CgqE,EAAuB5oE,GAAcA,EAAI,IAAM2oE,EAAU3oE,IAAMsB,EAAKtB,KAAOsB,EAAKtB,EAAI,GACpFilC,EAAajlC,GAAc2sB,GAAqByjC,EAAOpwD,GAAI,GAC3D6oE,EAAqD,GAC3DzY,EAAOvoD,QAAQ,CAACi5B,EAAG9gC,KACb2oE,EAAU3oE,IAAI6oE,EAAW/oE,KAAK,CAAEq2B,MAAOn2B,EAAG8oE,QAAS,CAACxnE,EAAKtB,QAE/D,MAAM2hB,EAAS3hB,GAEP,IAANA,EAAU,CAACilC,EAAU,GAAInY,GAAgBsjC,EAAO,KAAO,CAACtjC,GAAgBsjC,EAAOpwD,KACjF,GAAIgmC,GAAW,GAAKA,EAAUxlC,EAAQ,CAGpC,MAAMsoE,EAAUF,EAAoB5iC,GAChC,CAAC1kC,EAAK0kC,GAAUlZ,GAAgBsjC,EAAOpqB,KACvCrkB,EAAMqkB,GACV6iC,EAAW/oE,KAAK,CAAEq2B,MAAO6P,EAAS8iC,WACpC,CACA1Y,EAAOvoD,QAAQ,CAACrG,EAAGxB,KACb6tB,GAAqBrsB,IAAIqnE,EAAW/oE,KAAK,CAAEq2B,MAAOn2B,EAAG8oE,QAASnnD,EAAM3hB,OAEtEQ,EAAS,GAAGqoE,EAAW/oE,KAAK,CAAEq2B,MAAO,EAAG2yC,QAAS,CAAC7jC,EAAU,GAAI3jC,EAAK,MAGzE8uD,EAAOvoD,QAAQ,CAACi5B,EAAG9gC,KACb4oE,EAAoB5oE,IAAI6oE,EAAW/oE,KAAK,CAAEq2B,MAAOn2B,EAAG8oE,QAAS,CAACxnE,EAAKtB,QAGzE,MAAM+oE,EAA6C,GAC7CC,EAA0B,GAChC,IAAK,MAAM,MAAE7yC,EAAK,QAAE2yC,KAAaD,EAAY,CAC3C,GAAIG,EAAOppE,KAAM2kE,GAAMA,EAAEpuC,QAAUA,GAAQ,SAC3C,MAAMthB,EAAI2zD,GAAQryC,EAAO31B,GAAUkoE,EACnC,IAAK,MAAMtrE,KAAS0rE,EAAS,CAC3B,MAAMzc,EAzDS,EAyDLjvD,EAAMsD,OAEVg1D,EAAkB,IAAVv/B,EAActhB,EAAIshB,IAAU31B,EAAS,EAAIqU,EAAIw3C,EAAIx3C,EAAIw3C,EAAI,EACjEuJ,EAASF,EAAQrJ,EAIvB,IAHc0c,EAAMnpE,KACjBX,GAAMy2D,EAAQz2D,EAAE22D,OA7DD,GA6D6BA,EAAS32D,EAAEy2D,MA7DxC,GA+DlB,CACAqT,EAAMjpE,KAAK,CAAE41D,QAAOE,WACpBoT,EAAOlpE,KAAK,CAAEq2B,QAAO/4B,UACrB,KAHmB,CAIrB,CACF,CACA,OAAO4rE,CACT,CAEe,SAASC,KACtB,MAAM,MAAEt1B,EAAK,SAAEmK,GAAaE,MACtB,MAAEL,EAAK,cAAEurB,GC7GV,WACL,MAAOvrB,EAAOwrB,IAAY5uE,EAAAA,EAAAA,UAAmB,KACtCk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChC2uE,EAAeE,IAAoB7uE,EAAAA,EAAAA,UAAwB,MA4BlE,OA1BAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAoBhB,MAlBC,WACC,IACE,MAAM,KAAEuH,SzFibehB,EAAQrG,IAAoE,CACnGuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,iCACFsI,IyF1bH,GAAInD,EAAW,OAGf,MAAMoqB,EAAiB7iB,GAAM6iB,gBAAkB,GACzCuf,EAAOpiC,GAAMsxC,OAAS,G/E8BlC/vB,GAAsB,IAAIxtB,I+E7BG8uB,GACvBk6C,EAAiB36B,EAAKjwC,OAAQgD,IAAO0tB,EAAe1lB,SAAShI,IAAI6nE,IAAI,IAAM,MAC3EF,EAAS16B,EACX,CAAE,MACK3pC,GAAWqkE,EAAS,GAC3B,CAAE,QACKrkE,GAAW4qB,GAAW,EAC7B,C/EqBC,IV8Y8EznB,CyFlahF,EAhBA,GAkBM,KACLnD,GAAY,IAEb,IAEI,CAAE64C,QAAOluB,UAASy5C,gBAC3B,CD6EmCI,IAC1BC,EAASC,IAAcjvE,EAAAA,EAAAA,WAAS,GAEjCkvE,GAAWzsC,EAAAA,EAAAA,QAAuB,OACjC0sC,EAAcC,IAAmBpvE,EAAAA,EAAAA,UAAS,IAGjDC,EAAAA,EAAAA,WAAU,KACJmjD,EAAMj9C,OAAS,IACjBo9C,EAAS,CAAEn6C,KAAM,sBAAuBg6C,UAEnChK,EAAM+I,cACToB,EAAS,CAAEn6C,KAAM,WAAY0W,KAAM6uD,GAAiBvrB,EAAMA,EAAMj9C,OAAS,OAG5E,CAACi9C,IAEJ,MAAMisB,EAAej2B,EAAM+I,aACvB/I,EAAMgJ,eAAe2T,QAAQ3c,EAAM+I,cACnC/I,EAAMgJ,eAAej8C,OAAS,EAE5BmpE,GAAOz3D,EAAAA,EAAAA,aACV+jB,IACC,MAAM2zC,EAAUzsE,KAAKkX,IAAI,EAAGlX,KAAK8S,IAAIgmB,EAAOwd,EAAMgJ,eAAej8C,OAAS,IAC1E4nB,GAAY,kBAAmB,CAAElqB,MAAOu1C,EAAMgJ,eAAemtB,GAAU3zC,MAAO2zC,IAC9EhsB,EAAS,CAAEn6C,KAAM,WAAY0W,KAAMs5B,EAAMgJ,eAAemtB,MAE1D,CAACn2B,EAAMgJ,eAAgBmB,IAInBisB,GAAW/sC,EAAAA,EAAAA,QAAO4sC,GACxBG,EAAS5pC,QAAUypC,GAGnBpvE,EAAAA,EAAAA,WAAU,KACR,MAAMg5C,EAAiB9mC,IACrB,MAAM02C,EAAS12C,EAAE02C,OACX4mB,EAAM5mB,EAAO6mB,QAIjBv9D,EAAEsyD,kBACF5b,EAAO6b,UAAU,oBACT,UAAR+K,GACQ,aAARA,GACQ,WAARA,IAIY,cAAVt9D,EAAEvI,KACJuI,EAAEm/B,iBACFg+B,EAAKE,EAAS5pC,QAAU,IACL,eAAVzzB,EAAEvI,MACXuI,EAAEm/B,iBACFg+B,EAAKE,EAAS5pC,QAAU,MAI5B,OADAhZ,OAAOpY,iBAAiB,UAAWykC,GAC5B,IAAMrsB,OAAOnX,oBAAoB,UAAWwjC,IAClD,CAACq2B,KAIJrvE,EAAAA,EAAAA,WAAU,KACR,IAAK+uE,GAAW51B,EAAMgJ,eAAej8C,QAAU,EAAG,OAElD,MAAMhB,EAAKtE,YAAY,KACrB,MAAMytB,EAAOkhD,EAAS5pC,QAAU,GAAKwT,EAAMgJ,eAAej8C,OAAS,EAAIqpE,EAAS5pC,QAAU,EAC1F7X,GAAY,oBAAqB,CAAElqB,MAAOu1C,EAAMgJ,eAAe9zB,GAAOsN,MAAOtN,IAC7Ei1B,EAAS,CAAEn6C,KAAM,WAAY0W,KAAMs5B,EAAMgJ,eAAe9zB,MA1KrC,MA6KrB,MAAO,IAAMxtB,cAAcqE,IAC1B,CAAC6pE,EAAS51B,EAAMgJ,eAAgBmB,IAInC,MAAM97C,GAAQoC,EAAAA,EAAAA,SACZ,KAAMvC,EAAAA,GAAAA,IAAsB8xC,EAAMmJ,iBAClC,CAACnJ,EAAMmJ,kBAGHotB,EAAcv2B,EAAMgJ,eAAej8C,OAAS,EAUlD,IATAilE,EAAAA,EAAAA,iBAAgB,KACd,MAAM9lC,EAAM4pC,EAAStpC,QACrB,IAAKN,EAAK,OACV8pC,EAAgB9pC,EAAIsnC,aACpB,MAAMpkB,EAAY,IAAIC,eAAe,IAAM2mB,EAAgB9pC,EAAIsnC,cAE/D,OADApkB,EAAUh3B,QAAQ8T,GACX,IAAMkjB,EAAUE,cACtB,CAACinB,KAECA,EAAa,OAAO,KAEzB,MAAM9Z,EAASzc,EAAMgJ,eACfwtB,EAAa/Z,EAAOwZ,IAAiBj2B,EAAM+I,aAC3Cp+C,EAAiBuvB,GAAqBs8C,GAGtCvJ,GAAStiE,GAA4C,IAA1Bq1C,EAAMxlB,gBACjCi8C,EAAM9rE,EAAiB,cAAgBsiE,EAAQ,gBAAkB,gBACjEyJ,EAAaF,EAEf7rE,GAAkBsiE,GAAmC,IAA1BjtB,EAAMxlB,gB/E1JhC,SAA2BlC,GAChC,MAAMzqB,EAAI,IAAIkG,KAAKukB,EAAe,aAClC,MAAO,GAAGO,GAAehrB,EAAE4qB,eAAe5qB,EAAE6qB,eAC9C,C+EwJQi+C,CAAkBH,GAClBx9C,GAAqBw9C,EAAYx2B,EAAMxlB,iBAHzC,IAIEo8C,GACH52B,EAAMuJ,SAASz8B,MAAQkzB,EAAMuJ,SAASC,cAAcz8C,SAAW0vD,EAAO1vD,OACnE8oD,EAAQxpD,GAA6C,IAA5BwoE,GAAQxoE,EAAGowD,EAAO1vD,QAArB,IAE5B,OAGE,UAACwW,MAAAA,CAAI3a,UAAU,iG,WACb,SAACD,EAAAA,EAAMA,CACLN,KAAK,OACLO,UAAU,oCACVuX,aAAYy1D,EAAU,WAAa,WACnCpvD,QAAS,IAAMqvD,EAAYp1D,IAAOA,GAClCgL,SAAUgxC,EAAO1vD,QAAU,E,SAE1B6oE,GAAU,SAACiB,GAAAA,EAAKA,CAACjuE,UAAU,iBAAmB,SAACkuE,GAAAA,EAAIA,CAACluE,UAAU,mBAGjE,UAAC2a,MAAAA,CAAI3a,UAAU,wB,WACb,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVuX,aAAW,iBACXqG,QAAS,IAAM0vD,EAAKD,EAAe,GACnCxqD,SAAUwqD,GAAgB,E,UAE1B,SAACc,GAAAA,EAAWA,CAACnuE,UAAU,mBAEzB,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVuX,aAAW,mBACXqG,QAAS,IAAM0vD,EAAKD,EAAe,GACnCxqD,SAAUwqD,GAAgBxZ,EAAO1vD,OAAS,E,UAE1C,SAACiqE,GAAAA,EAAYA,CAACpuE,UAAU,sBAO5B,UAAC2a,MAAAA,CACC3a,UAAU,sEACVgc,cAAY,iB,WAEZ,SAACD,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,UACAuB,EAAiB,qCAAuC,yB,SAGzD8rE,IACK,KACR,SAAC9xD,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,gDACAuB,GAAkB,0B,SAGnB+rE,QAQL,UAACnzD,MAAAA,CAAI3a,UAAU,+B,WAIb,UAAC2a,MAAAA,CAAI6uB,eAAW,EAACxpC,UAAU,yB,UACxB6zD,EAAOjpD,IAAI,CAAC/I,EAAO4B,KAClB,SAACsY,OAAAA,CAECE,MAAOpa,EACP7B,WAAWQ,EAAAA,EAAAA,IACT,qCACA8wB,GAAqBzvB,GACjB,kEACsB,OAAtBA,EAAMQ,MAAM,EAAG,GACb,0CACA,iDAERoI,MAAO,CAAEk6B,KAAMsoB,EAAKxpD,KAVf5B,IAaRgyD,EAAOjpD,IAAI,CAAC/I,EAAO4B,KAClB,MAAM8B,EAAUE,EAAM5D,EAAMQ,MAAM,EAAG,IACrC,OAAKkD,GAEH,SAACwW,OAAAA,CAECC,cAAY,aACZC,MAAO1W,EAAQqF,IAAKvB,GAAMjI,GAAAA,GAAYiI,IAAIjE,KAAK,MAK/CpF,UAAU,oEACVyK,MAAO,CAAEk6B,KAAMsoB,EAAKxpD,KARf,SAAS5B,KAHG,WAkBzB,SAAC+pE,GAAMA,CACLr0D,aAAW,YACXvX,UAAU,gLACV0K,MAAO,CAAC2iE,GACRz5D,IAAK,EACLoE,IAAKlX,KAAKkX,IAAI,EAAG67C,EAAO1vD,OAAS,GACjCkqE,KAAM,EACN3sD,cAAgB,I,IAACvf,GAAE,E,OAAKmrE,EAAKnrE,IAC7BmsE,cAAgB,I,IAACnsE,GAAE,E,OAAKmrE,EAAKnrE,OAM/B,SAACwY,MAAAA,CACC3a,WAAWQ,EAAAA,EAAAA,IACT,+DACAwtE,GAAgB,aAElBhyD,cAAY,mB,SAEX63C,EAAOjpD,IAAI,CAAC/I,EAAO4B,KAClB,MAAMa,EAAU8yC,EAAMuJ,SAASC,cAAc3zC,SAASpL,GAChD0sE,EAAUn3B,EAAMuJ,SAASE,cAAc5zC,SAASpL,GAChD2sE,EAAY3a,EAAO1vD,OAAS,EAAI,IAAO0vD,EAAO1vD,OAAS,GAAK,GAC5DkoB,EAAQvrB,KAAKkX,IAAI,EAAGi0D,GAAQxoE,EAAGowD,EAAO1vD,QAAUqqE,GAChDC,EAAO3tE,KAAK8S,IAAI,EAAGq4D,GAAQxoE,EAAGowD,EAAO1vD,QAAUqqE,GACrD,OACE,SAACzyD,OAAAA,CAECC,cAAa1X,EAAU,eAAiB,iBACxC2X,MAAOpa,EACP7B,WAAWQ,EAAAA,EAAAA,IACT,kFACA8D,EAAU,gBAAkB,0BAE9BmG,MAAO,CAAEk6B,KAAiB,IAARtY,EAAH,IAAmBlV,MAA2B,KAAhBs3D,EAAOpiD,GAAX,K,SAExCkiD,IACC,SAACxyD,OAAAA,CACCC,cAAY,aACZhc,UAAU,+CAZT6B,QAmBb,SAAC8Y,MAAAA,CACC0a,IAAK63C,EACL1jC,eAAW,EACXxpC,UAAU,uD,SAETksE,GAAerY,EAAQwZ,EAAcF,GAtVnB,KAsVyDviE,IACzE,I,IAAA,MAAEgvB,EAAK,MAAE/4B,GAAO,E,OACf,SAACkb,OAAAA,CAIC/b,WAAWQ,EAAAA,EAAAA,IACT,mCACA,UAAUW,KAAKN,GAAS,QAAU,eAClC+4B,IAAUi6B,EAAO1vD,OAAS,GAAKy1B,EAAQ,EACnC,oBACAA,EAAQ,GAAK,mBACjBtI,GAAqBuiC,EAAOj6B,IACxB,yBACAA,IAAUyzC,GAAgB,eAC9BzzC,IAAUyzC,GAAgB,eAE5B5iE,MAAO,CAAEk6B,KAAMsoB,EAAKrzB,I,SAEnB/4B,GAhBI+4B,WAyBf,SAAC7d,OAAAA,CACCzE,KAAK,SACLtX,WAAWQ,EAAAA,EAAAA,IACT,sDACA42C,EAAMuJ,SAASz8B,KAAO,mBAAqB,yBAE7ClI,cAAY,kB,SAEXo7B,EAAMuJ,SAASz8B,KACZ,GAAGkzB,EAAMuJ,SAASz8B,2CAClBkzB,EAAMuJ,SAASG,kBACb,uBACA1J,EAAMuJ,SAASI,iBACb,0BAA0B3J,EAAMuJ,SAASC,cAAcz8C,UAAU0vD,EAAO1vD,WACxE,MAGV,UAACwW,MAAAA,CAAI3a,UAAU,6E,WACb,UAAC+b,OAAAA,CAAK/b,UAAU,4B,WACd,SAAC+b,OAAAA,CAAK/b,UAAU,yDAAyD,cAG3E,UAAC+b,OAAAA,CAAK/b,UAAU,4B,WACd,SAAC+b,OAAAA,CAAK/b,UAAU,oEAAoE,kBAGtF,UAAC+b,OAAAA,CAAK/b,UAAU,4B,WACd,SAAC+b,OAAAA,CAAK/b,UAAU,4C,UACd,SAAC+b,OAAAA,CAAK/b,UAAU,wCACX,+BAKX,UAAC2a,MAAAA,CAAI3a,UAAU,iE,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,iB,SAAiB,aACjC,UAACq8C,GAAMA,CACL3xC,MAAO+V,OAAO22B,EAAMxlB,iBACpBlQ,cAAgBvf,GAAMo/C,EAAS,CAAEn6C,KAAM,uBAAwBuoB,OAAQjrB,OAAOvC,K,WAE9E,SAACq6C,GAAaA,CAACjlC,aAAW,UAAUvX,UAAU,2C,UAC5C,SAACu8C,GAAWA,CAAAA,MAEd,SAACG,GAAaA,C,SACXsvB,GAAkBphE,IAAK8jE,IACtB,SAAC7xB,GAAUA,CAAiBnyC,MAAOgkE,EAAIhkE,MAAO1K,UAAU,c,SACrD0uE,EAAIxxD,OADUwxD,EAAIhkE,kBASnC,C,qCErYe,SAASikE,KACtB,MAAM,MAAEv3B,EAAK,SAAEmK,GAAaE,MACtB,QAAEhB,EAAO,KAAEpoC,EAAI,MAAE4L,EAAK,UAAE+I,EAAS,KAAE9I,GAASkzB,EAAMsJ,OAExD,IAAKroC,EACH,OACE,UAACsC,MAAAA,CAAI3a,UAAU,4C,WACb,SAAC2qD,GAAAA,EAAOA,CAAC3qD,UAAU,kCAAkC2Z,YAAa,OAClE,SAACi1D,KAAAA,CAAG5uE,UAAU,4B,SAA4B,kBAC1C,SAAC6X,IAAAA,CAAE7X,UAAU,mD,SAAmD,wIAI/DygD,GACC,SAAC1gD,EAAAA,EAAMA,CACLb,QAAQ,UACRc,UAAU,WACV4d,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,kB,SACjC,2BAID,SAACrH,EAAAA,EAAMA,CAACC,UAAU,WAAW4d,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,iB,SAAmB,yBAQxF,MAAMynE,EAAiB7hD,G9BQD8hD,E8BRqB9hD,EAAU0jC,S9BQfqe,E8BRwB/hD,EAAUgiD,iB9BUjEF,EAASC,EADW,M8BTgE,K9BQtF,IAAiBD,EAAgBC,EAxBTruE,EAYE8d,E8BM/B,OACE,UAAC7D,MAAAA,CAAI3a,UAAU,gB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,4D,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC4uE,KAAAA,CAAG5uE,UAAU,4B,SAA4B,WACzCikB,IAAS,SAAClI,OAAAA,CAAK/b,UAAU,gC,SAAgC,iBAE5D,UAAC2a,MAAAA,CAAI3a,UAAU,4B,WACb,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVic,MAAM,kBACN1E,aAAW,kBACXqG,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,iB,UAEhC,SAAC6nE,GAAAA,EAAMA,CAACjvE,UAAU,cAEpB,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACVic,MAAM,gBACN1E,aAAW,gBACXqG,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,kB,UAEhC,SAAC+iD,GAAAA,EAACA,CAACnqD,UAAU,oBAKnB,UAAC2a,MAAAA,CAAI3a,UAAU,sB,UACZkkB,IAAQ,SAACrM,IAAAA,CAAE7X,UAAU,2B,SAA4BkkB,KAEhDA,GAAQ8I,GAA0C,IAA7BA,EAAUkiD,QAAQ/qE,SACvC,SAAC0T,IAAAA,CAAE7X,UAAU,oC,SAAoC,mEAKjDkkB,GAAQ8I,GAAaA,EAAUkiD,QAAQ/qE,OAAS,IAChD,sB,WACE,UAACwW,MAAAA,CAAI3a,UAAU,yB,WACb,SAACmvE,GAAAA,CACCjyD,MAAM,4BACNxc,OAAQgtD,GAAa1gC,EAAUoiD,eAEjC,SAACD,GAAAA,CACCjyD,MAAM,oBACNxc,OAAQgtD,GAAa1gC,EAAUiiC,WAC/BogB,WAC4B,MAA1BriD,EAAUvO,cAGR,UAAC1C,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,iCACA4e,GAActe,KAAK4c,MAAMsP,EAAUvO,eAAe,I,W9B/DzCD,E8BkEOwO,EAAUvO,a9BjEtC,MAAND,EAAmB,IAChB,IAAG/d,EAAAA,EAAAA,GAAM+d,EAAI,EAAG,CAAE5d,WAAW,U8BgE0B,iBAE1C,WAKV,UAAC0uE,KAAAA,CAAGtvE,UAAU,wB,WACZ,SAACuvE,GAAAA,CAAMryD,MAAM,UAAUxc,OAAQgtD,GAAa1gC,EAAUwiD,cACtD,SAACD,GAAAA,CAAMryD,MAAM,oBAAoBxc,OAAQ8sD,GAAgBxgC,EAAU0jC,YACnE,SAAC6e,GAAAA,CACCryD,MAAM,YACNxc,OAAQ,G9B1FOA,E8B0FWssB,EAAUxP,oB9BzFpC,MAAV9c,EAAuB,IACpB,IAAGD,EAAAA,EAAAA,GAAMC,gB8BwFqE8sD,GACrExgC,EAAUmmB,6BAKG,MAAlB07B,GAA0BA,E9BlER,M8BmEjB,UAACh3D,IAAAA,CAAE7X,UAAU,gC,UACVc,KAAK4c,MAAuB,IAAjBmxD,GAAsB,kFAKrC7hD,EAAUkiD,QAAQ/qE,OAAS,IAC1B,UAACyW,QAAAA,CAAM5a,UAAU,iB,WACf,SAAC8a,QAAAA,CAAM9a,UAAU,wB,UACf,UAACkb,KAAAA,C,WACC,SAACE,KAAAA,CAAGpb,UAAU,6B,SAA6B,SAC3C,SAACob,KAAAA,CAAGpb,UAAU,8B,SAA8B,UAC5C,SAACob,KAAAA,CAAGpb,UAAU,8B,SAA8B,cAC5C,SAACob,KAAAA,CAAGpb,UAAU,8B,SAA8B,eAGhD,SAACgb,QAAAA,C,SACEgS,EAAUkiD,QAAQtkE,IAAK04B,IACtB,UAACpoB,KAAAA,CAAwBlb,UAAU,W,WACjC,SAACsb,KAAAA,CAAGtb,UAAU,mB,SAAoBsjC,EAAI2O,aACtC,SAAC32B,KAAAA,CAAGtb,UAAU,wB,SAAyB0tD,GAAapqB,EAAI8rC,eACxD,SAAC9zD,KAAAA,CAAGtb,UAAU,wB,SAAyB0tD,GAAapqB,EAAI2rB,cACxD,SAAC3zC,KAAAA,CAAGtb,UAAU,wB,SAAyBwtD,GAAgBlqB,EAAIotB,aAJpDptB,EAAImsC,sBAavBvrD,IAAS8I,IAAc/I,IACvB,SAACpM,IAAAA,CAAE7X,UAAU,oC,SAAoC,kDAO3D,CAEA,SAASmvE,GAAU,G,IAAA,MACjBjyD,EAAK,OACLxc,EAAM,WACN2uE,GAHiB,EASjB,OACE,UAAC10D,MAAAA,C,WACC,SAAC9C,IAAAA,CAAE7X,UAAU,gC,SAAiCkd,KAC9C,SAACrF,IAAAA,CAAE7X,UAAU,4B,SAA6BU,IACzC2uE,IAAc,SAACx3D,IAAAA,CAAE7X,UAAU,iB,SAAkBqvE,MAGpD,CAEA,SAASE,GAAM,G,IAAA,MAAEryD,EAAK,OAAExc,GAAT,EACb,OACE,UAACia,MAAAA,CAAI3a,UAAU,6B,WACb,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAyBkd,KACvC,SAACyyD,KAAAA,CAAG3vE,UAAU,Q,SAASU,MAG7B,C,eC3PO,SAASkvE,GAAcC,EAAexrE,EAAgBJ,GAC3D,GAAIA,GAAU,EAAG,OAAO,KACxB,OAAQ4rE,GACN,IAAK,aACL,IAAK,YACH,OAAQxrE,EAAS,EAAIJ,GAAUA,EACjC,IAAK,YACL,IAAK,UACH,OAAQI,EAAS,EAAIJ,GAAUA,EACjC,IAAK,OACH,OAAO,EACT,IAAK,MACH,OAAOA,EAAS,EAClB,QACE,OAAO,KAEb,CCUO,MAAM6rE,GAAmD,CAC9DrnD,IAAK,MACLE,IAAK,OACL/iB,IAAK,MACLijB,IAAK,MACLgU,SAAU,YAICkzC,GAAkD,CAC7DtnD,IAAK,MACLE,IAAK,qBACL/iB,IAAK,MACLijB,IAAK,MACLgU,SAAU,qBAONmzC,GAAU,CACd,CAAE5yC,MAAO,SAAU6yC,KAAM,UACzB,CAAE7yC,MAAO,MAAO6yC,KAAM,MAAOnoC,IAAK,mBAClC,CAAE1K,MAAO,MAAO6yC,KAAM,OAAQnoC,IAAK,mBACnC,CAAE1K,MAAO,WAAY6yC,KAAM,WAAYnoC,IAAK,mBAUxCooC,GAAY,CAChB,CAAC,WAAY,WACb,CAAC,UAAW,WACZ,CAAC,OAAQ,SAgBJ,SAASC,GACd1uE,EACAq7B,EACA4+B,GAEA,GAAmB,aAAfj6D,EAIF,MAAO,CACL2uE,YAAa,mBAAmB1U,MAJpBrnC,GAAkBzpB,IAC7BvB,GAAM,GAAGA,EAAE+Q,SAASwa,GAAmBvrB,EAAEirB,OAAO/a,QAAQ,OAGNnU,KAAK,SACxDinB,OAAO5rB,EAAAA,EAAAA,IAAO,GACdguE,MAAMhuE,EAAAA,EAAAA,GAAM,EAAG,EAAG,CAAEG,WAAW,KAGnC,KAAKg3D,EAAAA,GAAAA,IAAmBn2D,GAAa,CACnC,MAAMgzB,GAAQ47C,EAAAA,GAAAA,SAAezkE,EAAW,IACxC,MAAO,CACLwkE,YAAa,mBAAmB1U,MAAajnC,EAAM7pB,IAAKvB,GAAMA,EAAE+Q,OAAOhV,KAAK,SAC5EinB,OAAO5rB,EAAAA,EAAAA,GAAMg0B,EAAM,GAAGuJ,OACtBywC,MAAMhuE,EAAAA,EAAAA,GAAMg0B,EAAMA,EAAMtwB,OAAS,GAAG65B,MAAO,EAAG,CAAEp9B,WAAW,IAE/D,CACA,GAAiB,SAAbk8B,EACF,MAAO,CACLszC,YAAaE,GACXC,GAAAA,GAAUluE,QACPmuE,UACA5lE,IAAKlI,GAAMA,EAAE0X,OAChBshD,GAGFrvC,MAAOkkD,GAAAA,GAAUA,GAAAA,GAAUpsE,OAAS,GAAG+Y,MACvCuxD,KAAM8B,GAAAA,GAAU,GAAGrzD,OAGvB,MAAMuzD,EAAOC,GAAAA,GAAgBruE,QAAQmuE,UACrC,MAAO,CACLJ,YACe,aAAbtzC,EACIwzC,GACEG,EAAK7lE,IAAKlI,GAAMA,EAAE0X,OAClBshD,GAEF,mBAAmBA,MAAa+U,EAC7B7lE,IAAKlI,GAAM,GAAGA,EAAE0X,SAASu2D,GAAgBjuE,EAAEghC,QAAQnqB,QAAQ,OAC3DnU,KAAK,SACdinB,OAAO5rB,EAAAA,EAAAA,IAAO,GACdguE,MAAMhuE,EAAAA,EAAAA,GAAM,EAAG,EAAG,CAAEG,WAAW,IAEnC,CAGA,SAAS0vE,GAAcM,EAAmBlV,GACxC,MAAMtnC,EAAIw8C,EAAQzsE,OAKlB,MAAO,mBAAmBu3D,MAJZkV,EAAQ9rB,QAAQ,CAACvb,EAAO9lC,IAAM,CAC1C,GAAG8lC,MAAe,IAAL,EAAYnV,GAAG7a,QAAQ,MACpC,GAAGgwB,MAAqB,KAAT9lC,EAAI,GAAY2wB,GAAG7a,QAAQ,QAECnU,KAAK,QACpD,CAGA,SAASurE,GAAgBr8C,GACvB,OAASA,EAAQ,KAAO,EAAK,GAC/B,CAEA,SAASu8C,KAGP,MAAMp8C,GAAQ47C,EAAAA,GAAAA,SAAezkE,EAAW,IACxC,OAAO6oB,EACJ7pB,IAAI,CAACvB,EAAG5F,KAAO,CAAEwoE,QAAS,GAAMx3C,EAAMtwB,OAAS,GAAM,IAAK65B,MAAO30B,EAAE20B,MAAOv6B,OAC1ExB,OAAQoH,GAAMA,EAAE5F,EAAI,GAAM,GAC1BmH,IAAKvB,IAAO,CAAE4iE,QAAS5iE,EAAE4iE,QAAS/uD,OAAOzc,EAAAA,EAAAA,GAAM4I,EAAE20B,MAAO,EAAG,CAAEp9B,WAAW,MAC7E,CAgBA,SAASkwE,KAGP,MAAM18C,EAAIs8C,GAAAA,GAAgBvsE,OAC1B,MAAO,CACL,CAAC,GAAI,GACL,CAAC,GAAI,IACL,CAAC,EAAG,GACJ,CAAC,EAAG,IACJ,CAAC,GAAI,IACLyG,IAAK,I,IAACmmE,EAAOztE,GAAE,E,MAAM,CACrB2oE,QAAS,EAAS73C,EAAK,IACvBlX,MAAO,IAAGzc,EAAAA,EAAAA,GAAM6C,EAAG,EAAG,CAAE1C,WAAW,IAAQoN,QAAQ,MAAO,UAE9D,CAEA,SAASgjE,GAAQ,G,IAAA,QAAEC,GAAF,EACf,OACE,SAACt2D,MAAAA,CAAI3a,UAAU,mD,SACZixE,EAAQrmE,IAAKvB,IACZ,SAAC0S,OAAAA,CAEC/b,WAAWQ,EAAAA,EAAAA,IACT,mCAGA6I,EAAE4iE,SAAW,EAAI,SAAW5iE,EAAE4iE,SAAW,IAAM,UAAY,oBAE7DxhE,MAAOpB,EAAE4iE,QAAU,GAAK5iE,EAAE4iE,QAAU,IAAM,CAAEtnC,KAAM,GAAGt7B,EAAE4iE,iBAAergE,E,SAErEvC,EAAE6T,OATE7T,EAAE6T,SAcjB,CAGA,SAASg0D,GAAO,G,IAAA,KAAET,GAAF,EACd,OACE,SAAC91D,MAAAA,CAAI3a,UAAU,yC,SACZywE,EAAK7lE,IAAKumE,IACT,MAAMj0D,EAAQi0D,EAAIj0D,MAAMlP,QAAQ,KAAM,KACtC,OACE,SAAC+N,OAAAA,CAECzE,KAAK,MACLC,aAAY2F,EACZjB,MAAOiB,EACPld,UAAU,SACVyK,MAAO,CAAE2mE,gBAAiBD,EAAI/2D,QALzB+2D,EAAIj0D,UAWrB,CAEA,SAASknB,KACP,MAAM,MAAEgT,GAAUqK,KACZ4vB,IAAUzZ,EAAAA,GAAAA,IAAmBxgB,EAAM4I,gBACnCz5B,EAAOupD,GAAgB14B,EAAM4I,gBAC7BsxB,EAAMvB,GAAe34B,EAAM4I,gBAEjC,GAA6B,aAAzB5I,EAAM4I,eACR,OACE,UAACrlC,MAAAA,CAAI3a,UAAU,sB,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,4C,SAA4C,6CAG5D,SAAC2a,MAAAA,CACC3a,UAAU,oBACVyK,MAAO,CAAEi6B,WAAYyrC,GAAY,WAAY/4B,EAAMta,SAAU,YAAYszC,gBAE3E,SAACY,GAAAA,CAAQC,QAhFR,EAAE,GAAI,GAAK,EAAG,GAAK,GAAGrmE,IAAKtH,IAAO,CACvC2oE,QAASr3C,GAAmBtxB,GAC5B4Z,MAAO,IAAGzc,EAAAA,EAAAA,GAAM6C,EAAGoB,OAAO6sE,UAAUjuE,GAAK,EAAI,EAAG,CAAE1C,WAAW,gBAmF/D,GAAIywE,EACF,OACE,UAAC12D,MAAAA,CAAI3a,UAAU,sB,WACb,UAAC+b,OAAAA,CAAK/b,UAAU,4C,UAA4C,oBAAkBumB,MAC9E,SAAC5L,MAAAA,CACC3a,UAAU,oBACVyK,MAAO,CACLi6B,WAAYyrC,GAAY/4B,EAAM4I,eAAgB5I,EAAMta,SAAU,YAAYszC,gBAG9E,SAACY,GAAAA,CAAQC,QAASJ,UAKxB,GAAuB,SAAnBz5B,EAAMta,SAAqB,CAC7B,MAAM2zC,EAAOF,GAAAA,GAAUluE,QAAQmuE,UAC/B,OACE,UAAC71D,MAAAA,CAAI3a,UAAU,sB,WACb,UAAC+b,OAAAA,CAAK/b,UAAU,4C,UAA4C,cAC9CsxE,EAAI,+BAElB,SAACJ,GAAAA,CAAOT,KAAMA,KACd,SAAC91D,MAAAA,CAAI3a,UAAU,2C,SACZywE,EAAK7lE,IAAKumE,IACT,SAACp1D,OAAAA,CAAqB/b,UAAU,uC,SAC7BmxE,EAAIj0D,OADIi0D,EAAIj0D,YAOzB,CAEA,OACE,UAACvC,MAAAA,CAAI3a,UAAU,sB,WACb,UAAC+b,OAAAA,CAAK/b,UAAU,4C,UAA4C,mBAAiBsxE,KACzD,aAAnBl6B,EAAMta,UACL,SAACo0C,GAAAA,CAAOT,KAAMC,GAAAA,GAAgBruE,QAAQmuE,aAEtC,SAAC71D,MAAAA,CACC3a,UAAU,oBACVyK,MAAO,CACLi6B,WAAYyrC,GAAY/4B,EAAM4I,eAAgB,UAAW,YAAYowB,gBAI3E,SAACY,GAAAA,CAAQC,QAA4B,aAAnB75B,EAAMta,SAA0Bg0C,KA3I/C,EAAE,GAAI,EAAG,EAAG,EAAG,GAAGlmE,IAAKtH,IAAO,CACnC2oE,QAAS0E,GAAgBrtE,GACzB4Z,MAAO,IAAGzc,EAAAA,EAAAA,GAAM6C,EAAG,EAAG,CAAE1C,WAAW,eA4IvC,CAOA,SAAS4wE,KACP,MAAM,MAAEp6B,GAAUqK,KACZ6vB,EAAMvB,GAAe34B,EAAM4I,gBAEjC,GAA6B,aAAzB5I,EAAM4I,eACR,OACE,SAACjkC,OAAAA,CAAK/b,UAAU,6C,SAA6C,+GAMjE,KAAK43D,EAAAA,GAAAA,IAAmBxgB,EAAM4I,gBAC5B,OAAO,SAACjkC,OAAAA,CAAK/b,UAAU,6C,SAA6C,oBAEtE,GAAuB,SAAnBo3C,EAAMta,UAAgD,QAAzBsa,EAAM4I,eAGrC,OACE,SAACjkC,OAAAA,CAAK/b,UAAU,6C,SAA6C,oJAMjE,GAAuB,SAAnBo3C,EAAMta,SACR,OACE,UAAC/gB,OAAAA,CAAK/b,UAAU,6C,UAA6C,sCACvBsxE,EAAI,OAK9C,MAAMlsD,EAAQgyB,EAAM+I,aAChB,IAAIh1C,KAAK,GAAGisC,EAAM+I,yBAAyBrkC,mBAAmB,QAAS,CACrE0H,MAAO,OACPC,KAAM,YAER,KACJ,OACE,UAAC1H,OAAAA,CAAKzE,KAAK,OAAOtX,UAAU,8C,WAC1B,SAAC+b,OAAAA,CAAK/b,UAAU,c,SAAc,8BAAgC,oDACtCsxE,EAAI,QAC3BlsD,EAAQ,oCAAoCA,IAAU,GAAG,MAGhE,CASA,SAASqsD,GAAM,G,IAAA,OAAEC,GAAF,EACb,MAAM,MAAEt6B,EAAK,SAAEmK,GAAaE,KAE5B,GAA6B,aAAzBrK,EAAM4I,eAA+B,OAAO,KAChD,MAAMqxB,IAAUzZ,EAAAA,GAAAA,IAAmBxgB,EAAM4I,gBAEnCyM,EAAS4kB,EACX,CAEI,CAAC,MAAO,OACR,CAAC,MAAO,QAEVzmE,IAAK,I,IAAC+mE,EAAMz0D,GAAM,E,MAAM,CACxBA,QACAusB,QAAS2N,EAAM4I,iBAAmB2xB,EAClCC,KAAM,IAAMF,EAAOC,MAErBzB,GAAUtlE,IAAK,I,IAACkyB,EAAU5f,GAAM,E,MAAM,CACpCA,QACAusB,QAAS2N,EAAMta,WAAaA,EAC5B80C,KAAM,IAAMrwB,EAAS,CAAEn6C,KAAM,eAAgB01B,gBAGnD,OACE,SAACniB,MAAAA,CACCrD,KAAK,QACLC,aAAY85D,EAAS,OAAS,cAC9Bp1D,MACEo1D,OACIzlE,EACA,2LAEN5L,UAAU,oD,SAETysD,EAAO7hD,IAAKinE,IACX,SAACnoC,SAAAA,CAECtiC,KAAK,SACLuiC,eAAckoC,EAAMpoC,QACpB7rB,QAASi0D,EAAMD,KACf5xE,WAAWQ,EAAAA,EAAAA,IACT,4DACAqxE,EAAMpoC,QACF,uCACA,+C,SAGLooC,EAAM30D,OAXF20D,EAAM30D,SAgBrB,CAUA,SAAS40D,KACP,MAAM,MAAE16B,GAAUqK,KAClB,OAAKrK,EAAM3vC,OAAO23C,iBAAoB6S,GAAe7a,EAAM4I,iBAGzD,UAACrlC,MAAAA,CAAI3a,UAAU,wB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,8BAA8ByK,MAAO,CAAEsnE,YAAa1f,OACpE,UAACt2C,OAAAA,C,UAAK,mBzBlZoB,GyBmZY/a,eAAe,SAAS,gCAGhE,UAAC+a,OAAAA,C,UAAK,4BzB7Y2B,EyB6YuB,mCAVuB,IAarF,CAOA,SAASi2D,KACP,MAAM,MAAE56B,GAAUqK,KAClB,OAAKC,GAAetK,EAAO,QAEzB,UAACz8B,MAAAA,CAAI3a,UAAU,wB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC+b,OAAAA,CACC/b,UAAU,4CACVyK,MAAO,CAAEsnE,YAAajyD,OAExB,SAAC/D,OAAAA,C,SAAM2D,GAAiBE,UAE1B,SAAC7D,OAAAA,C,SAAK,mDAVgC,IAa5C,CAGA,SAASk2D,KACP,MAAM,MAAE76B,GAAUqK,KAClB,IAAKrK,EAAM3vC,OAAOm4C,SAAU,OAAO,KACnC,MAAMj+C,GAAQG,EAAAA,GAAAA,IACZs1C,EAAMmJ,gBACNnJ,EAAM+I,aACN7uB,GAAqB8lB,EAAM+I,eAC3B3+C,EAAAA,GAAAA,IAAY41C,EAAM4I,iBAEpB,OAAKr+C,GACE,SAACoa,OAAAA,C,UAAM1W,EAAAA,GAAAA,IAAa1D,KADR,IAErB,CAce,SAASuwE,GAAmB,G,IAAA,KAAEP,EAAI,OAAED,EAAM,YAAES,GAAhB,EACzC,MAAM,MAAE/6B,EAAK,SAAEmK,GAAaE,KACtBrkB,EAjbgB,SADN37B,EAkbO21C,EAAM4I,iBAjbiB,QAAfv+C,EAAuB,SAAWA,EADnE,IAAkBA,EAobhB,MAAM2wE,EAAY3sE,IAChBisE,EAAOjsE,GACP87C,EAAS,CAAEn6C,KAAM,sBAAuBkjB,MAAO7kB,KAE3C4sE,EAAa3vE,GACX,WAANA,EAAiB0vE,EAAST,GAAQpwB,EAAS,CAAEn6C,KAAM,sBAAuBkjB,MAAO5nB,IAC7E4vE,EAAetC,GAAQtsE,UAAWhB,GAAMA,EAAE06B,QAAUA,GAEpDm1C,EAAa,CACQ,QAAzBn7B,EAAM4I,iBACJ,SAACjkC,OAAAA,C,SAAe,gJAAN,QAKZ,SAAC+1D,GAAAA,CAAAA,EAAiB,YAClB,SAACE,GAAAA,CAAAA,EAAmB,cACpB,SAACC,GAAAA,CAAAA,EAAkB,aAGrB,OACE,UAAC/rC,UAAAA,CACC3uB,aAAW,aACXvX,UAAU,uE,WAEV,UAAC2a,MAAAA,CAAI3a,UAAU,oC,WACb,SAAC4nC,KAAAA,CAAG5nC,UAAU,4B,SAA4B,gBAC1C,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,+BACVuX,aAAW,qBACX0E,MAAM,YACN2B,QAASu0D,E,UAET,SAACK,GAAAA,EAAaA,CAACxyE,UAAU,sBAI7B,SAAC2a,MAAAA,CACCrD,KAAK,aACLC,aAAW,kBACXvX,UAAU,2DACVgqD,UAAY75C,IACV,MAAM1M,EAAImsE,GAAcz/D,EAAEvI,IAAK0qE,EAActC,GAAQ7rE,QAC5C,MAALV,IACJ0M,EAAEm/B,iBACF+iC,EAAUrC,GAAQvsE,GAAG25B,OACrBjtB,EAAEsiE,cAAcC,iBAA8B,kBAAkBjvE,IAAIkvE,U,SAGrE3C,GAAQplE,IAAKlI,IACZ,MAAM+mC,EAAU/mC,EAAE06B,QAAUA,EAC5B,OACE,UAACsM,SAAAA,CAECtiC,KAAK,SACLkQ,KAAK,QACLs7D,eAAcnpC,EACdkgB,SAAUlgB,EAAU,GAAK,EACzB7rB,QAAS,IAAMy0D,EAAU3vE,EAAE06B,OAC3Bp9B,WAAWQ,EAAAA,EAAAA,IACT,gGACAipC,EAAU,0BAA4B,yB,WAGxC,SAAC1tB,OAAAA,CAAK/b,WAAWQ,EAAAA,EAAAA,IAAG,4BAA6BipC,GAAW,gB,SACzD/mC,EAAEutE,QAGL,SAACl0D,OAAAA,CAAK/b,UAAU,8D,SACD,WAAZ0C,EAAE06B,MAAqB0yC,GAAgB6B,GAAQjvE,EAAEolC,QAhB/CplC,EAAE06B,YAuBf,SAACgH,GAAOA,CAAAA,IAER,UAACzpB,MAAAA,CAAI3a,UAAU,4B,WACb,SAAC2a,MAAAA,CAAI3a,UAAU,iB,UACb,SAACwxE,GAAAA,CAAAA,MAEH,SAACC,GAAAA,CAAMC,OAAQU,OAGG,SAAnBh7B,EAAMta,WAAuB86B,EAAAA,GAAAA,IAAmBxgB,EAAM4I,kBACrD,SAACnoC,IAAAA,CAAE7X,UAAU,6C,SAA6C,qJAM5D,SAAC2a,MAAAA,CAAI3a,UAAU,kI,SACZuyE,MAIT,CC1jBA,MAAMM,GAAmE,CACvE,CAAEnoE,MAAO,QAASwS,MAAO,QAAS0hB,MAAO,uDACzC,CACEl0B,MAAO,YACPwS,MAAO,QACP0hB,MAAO,uDAET,CACEl0B,MAAO,YACPwS,MAAO,YACP0hB,MAAO,wDAILk0C,GAAgB,8BAA6BzC,EAAAA,GAAAA,SAAezkE,EAAW,GAC1EhB,IAAKvB,GAAMA,EAAE+Q,OACbhV,KAAK,SAGR,SAAS8zD,GAAK3vB,G,IAAe,WAAEwpC,GAAa,EAAK,QAAEC,EAAU,GAAhC,uDAAsC,CAAC,EAClE,MAAO,CACL77D,MAAO,GACPC,OAAQ,GACR67D,aAAc,EACdC,OAAQ,OAAOH,EAAa,SAAW,WAAWxpC,IAClD6nC,gBAAiB4B,EACb,sBAAsBzpC,KAASypC,mBAC/B,cAER,CAEA,SAASjqC,GAAKqnC,EAAqB+C,GACjC,MAAO,CAAEh8D,MAAO,GAAIC,OAAQ+7D,EAAOF,aAAc,EAAGvuC,WAAY0rC,EAClE,CAEA,MAAMgD,GAAyC,CAC7CzzD,SAAU,CAAExI,MAAO,GAAIC,OAAQ,GAAI67D,aAAc,EAAGvuC,WAAYouC,IAChElzD,IAAKs5C,GAAKp5C,GAAmB,CAAEizD,YAAY,IAC3C1zB,aAAc6Z,GAAKnB,IACnB7Y,gBAAiB,CACf/nC,MAAO,GACPC,OAAQ,GACR67D,aAAc,MACdvuC,WAAYm0B,GAAyBzxB,IACrC8rC,OAAQ,oBACRG,UAAW,6BAEb9zB,YAAa2Z,GAAKnB,GAAwB,CAAEgb,YAAY,EAAMC,QAAS,KACvExzB,eAAgB0Z,GAAKnB,GAA2B,CAAEgb,YAAY,IAC9DrzB,cAAewZ,GAAKnB,GAA0B,CAAEib,QAAS,KAGzDpzB,SAAU,CACRzoC,MAAO,GACPC,OAAQ,GACR67D,aAAc,EACdvuC,WAAYouC,GACZI,OAAQ,kCAEV5zB,SAAU4Z,GAAKnB,GAAqB,CAAEib,QAAS,KAC/CvzB,OAAQyZ,GAAKnB,GAAmB,CAAEgb,YAAY,EAAMC,QAAS,KAC7DnzB,OAAQ,CACN1oC,MAAO,GACPC,OAAQ,GACR67D,aAAc,EACdvuC,WAAY,6CAEdya,cAAe,CAAEhoC,MAAO,GAAIC,OAAQ,GAAI67D,aAAc,EAAGC,OAAQ,qBACjE9zB,gBAAiBrW,GAAKspB,GAAe,GACrC1S,kBAAmB5W,GACjB,6BAA6BsvB,GAAWC,mBAAmBD,GAAWE,cACtE,IAWS+a,GAA4B,CACvC,WACA,SACA,SACA,gBACA,kBACA,qBAIF,SAASC,GAAQn8B,EAAmB1iB,GAClC,MAAa,oBAATA,EACK0iB,EAAM3vC,OAAO23C,iBAAmB6S,GAAe7a,EAAM4I,gBAEvD5I,EAAM3vC,OAAOitB,EACtB,CAEA,SAAS28C,GAAOj6B,GACd,MAAgC,QAAzBA,EAAM4I,gBAAqD,QAAzB5I,EAAM4I,cACjD,CAQe,SAASwzB,GAAc,G,IAAA,KAAEC,EAAI,OAAEC,GAAR,EACpC,MAAM,MAAEt8B,EAAK,SAAEmK,GAAaE,KACtBkwB,EAAO7B,GAAgB14B,EAAM4I,gBAC7BrgC,EAAW+hC,GAAetK,EAAO,YACjCwY,EAAU1O,GAAuB,IAAMK,EAAS,CAAEn6C,KAAM,eAAgB85C,UAUxEyyB,EAAUL,GAAWrxE,OAAQyyB,GAAS6+C,GAAQn8B,EAAO1iB,IAAOvwB,OAC5DyvE,EACJx8B,EAAM6H,YAAY96C,OAVc,CAChC,eACA,kBACA,cACA,iBACA,gBACA,YAIsClC,OAAQyyB,GAAS6+C,GAAQn8B,EAAO1iB,IAAOvwB,OAASwvE,EAElFj/C,EAAO,CAACgK,EAAsBnY,EAAcstD,KAChD,SAACC,GAAAA,CACCvtD,KAAMA,EACNwtD,QAASX,GAAQ10C,GACjB/S,IAAK4nD,GAAQn8B,EAAO1Y,GACpBs1C,SAAUpkB,EAAOlxB,GACjBm1C,OAAQA,IAIZ,OACE,SAACv+B,GAAeA,CAACC,cAAe,I,UAC9B,UAAC56B,MAAAA,CAAI3a,UAAU,gB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,+B,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,iE,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gB,SAAgB,iBAChC,UAAC+b,OAAAA,CAAK/b,UAAU,Q,UAAS4zE,EAAU,cAGrC,SAACE,GAAAA,CACCvtD,KAAM7G,GAAiBC,SACvBo0D,QAASX,GAAQzzD,SACjBgM,IAAKhM,EACLq0D,SAAU,IAAMzyB,EAAS,CAAEn6C,KAAM,mBAAoB4Y,UAAW,aAChE6zD,OAAO,gDAET,SAACC,GAAAA,CACCvtD,KAAM7G,GAAiBE,IACvBm0D,QAASX,GAAQxzD,IACjB+L,IAAK+1B,GAAetK,EAAO,OAC3B48B,SAAU,IAAMzyB,EAAS,CAAEn6C,KAAM,mBAAoB4Y,UAAW,QAChE6zD,OAAO,2EAEqB,IAA7Bz8B,EAAM6H,YAAY96C,SACjB,SAAC0T,IAAAA,CAAE7X,UAAU,0C,SAA0C,wDAKxD00B,EAAK,eAAgB,gBACrB0iB,EAAM3vC,OAAO43C,eACZ,UAAC3V,SAAAA,CACCtiC,KAAK,SACLkQ,KAAK,SACLs7D,eAAcx7B,EAAMiJ,mBACpBx9B,UAAWlD,EACX/B,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,gBAChCpH,UAAU,0K,WAEV,SAAC+b,OAAAA,CAAK/b,UAAU,iB,SAAiB,sBAG/B2f,IAAY,SAAC5D,OAAAA,CAAK/b,UAAU,W,SAAW,mCACzC,SAACi0E,GAAAA,CAAWtoD,IAAKyrB,EAAMiJ,mBAAoB6zB,eAAgBv0D,OAG9D+U,EACC,kBACA,yBACA,gEAEDA,EAAK,cAAe,cACpBA,EAAK,iBAAkB,kBACvBA,EAAK,gBAAiB,iBACtBA,EACC,WACA,mBACA28C,GAAOj6B,GACH,iMACA,uDAGN,UAAC1N,SAAAA,CACCtiC,KAAK,SACL+sE,gBAAeV,EACf71D,QAAS,IAAM81D,GAAQD,GACvBzzE,UAAU,6G,WAEV,SAAC+b,OAAAA,CAAK/b,UAAU,+B,UACd,SAACouE,GAAAA,EAAYA,CAACpuE,WAAWQ,EAAAA,EAAAA,IAAG,8BAA+BizE,GAAQ,iBAEpEA,EACG,eACA,eAAeH,GAAWnvE,SAASwvE,EAAU,KAAKA,QAAgB,UAMtEF,IACA,sB,WACE,SAACW,GAAAA,CAAY1/C,KAAK,gBAAgBnO,KAAK,mBACvC,SAAC6tD,GAAAA,CAAY1/C,KAAK,kBAAkBnO,KAAK,oBACxC6wB,EAAM3vC,OAAOk4C,oBAAqB,SAAC00B,GAAAA,CAA2BC,SAAO,OAIzEb,IACC,sB,UACG/+C,EACC,WACA,WACA,yEAEDA,EAAK,SAAU,SAAU,oDACzBA,EACC,SACA,SACA,yJAEDA,EACC,gBACA,gBACA28C,GAAOj6B,GACH,eAAeu6B,wCACU,aAAzBv6B,EAAM4I,eAEJ,sDACyB,QAAzB5I,EAAM4I,eACJ,sEACA,kEAEV,SAACo0B,GAAAA,CAAY1/C,KAAK,mBAClB,SAACo/C,GAAAA,CACCvtD,KAAK,kBACLwtD,QAASX,GAAQh0B,gBACjBzzB,IAAK4nD,GAAQn8B,EAAO,mBACpB48B,SAAUpkB,EAAO,mBACjBskB,eAAgBjiB,GAAe7a,EAAM4I,gBACrC34B,MAAM,oBACNwsD,OACE5hB,GAAe7a,EAAM4I,gBACjB,qB1B3PY,G0B2P4Bh/C,eAAe,iBAAiB2wE,qBACxE,6HAGR,SAACyC,GAAAA,CAAY1/C,KAAK,oBACjBA,EACC,oBACA,oBACA,wGAED0iB,EAAM3vC,OAAOk4C,oBAAqB,SAAC40B,GAAAA,CAAAA,UAK1C,UAAC55D,MAAAA,CAAI3a,UAAU,qD,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,8C,SAA8C,gBAC9D,SAAC2a,MAAAA,CAAI3a,UAAU,yB,SACZ6yE,GAAajoE,IAAKjB,IACjB,MAAM8/B,EAAU2N,EAAMjwC,UAAYwC,EAAEe,MACpC,OACE,UAACg/B,SAAAA,CAECtiC,KAAK,SACLuiC,eAAcF,EACd7rB,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,cAAeD,QAASwC,EAAEe,QAC1D1K,WAAWQ,EAAAA,EAAAA,IACT,oDACAipC,EAAU,eAAiB,+C,WAG7B,SAAC1tB,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,kBACAipC,EACI,2DACA,mCAENh/B,MAAO,CAAEi6B,WAAY/6B,EAAEi1B,SAExBj1B,EAAEuT,QAlBEvT,EAAEe,kBA2BzB,CAGA,SAAS6pE,KACP,OACE,UAAC55D,MAAAA,CAAI3a,UAAU,qC,WACb,UAACymC,KAAAA,CACClvB,aAAW,4BACXvX,UAAU,sD,UAETo4D,GAAcxtD,IAAK4pE,IAClB,UAAC9tC,KAAAA,CAAc1mC,UAAU,4B,WACvB,SAAC+b,OAAAA,CACC/b,UAAU,gCAIVyK,MAAO,CAAEsnE,YAAa1Z,GAAWmc,GAAOC,QAAS/b,GAAa8b,OAEhE,SAACz4D,OAAAA,C,SAAM48C,GAAY6b,OARZA,KAcX,UAAC9tC,KAAAA,CAAG1mC,UAAU,4B,WACZ,SAAC+b,OAAAA,CACC/b,UAAU,8CACVyK,MAAO,CAAEsnE,YAAa1Z,GAAWC,SAAUmc,QAAS/b,GAAaJ,aAEnE,SAACv8C,OAAAA,C,SdzSmB,uDc4SxB,SAACs4D,GAAAA,CAAAA,KAGP,CAWA,SAASA,GAA2B,G,IAAA,QAAEC,GAAU,GAAZ,EAClC,MAAM,MAAEl9B,GAAUqK,KACZN,EAAU/J,EAAM2I,cAAcJ,kBAC9Bz7B,EAAOkzB,EAAM0I,WAAWH,kBAC9B,OAAKwB,GAAYj9B,GAGf,UAACvJ,MAAAA,CAAI3a,WAAWQ,EAAAA,EAAAA,IAAG,sBAAuB8zE,GAAW,kB,UAIlDnzB,IACC,UAACtpC,IAAAA,CAAE7X,UAAU,6C,UACVs0E,IAAW,SAACv4D,OAAAA,CAAK/b,UAAU,c,SAAc,wBACzCmhD,KAGJj9B,IAAQ,SAACrM,IAAAA,CAAE7X,UAAU,wC,SAAyCkkB,OAbrC,IAgBhC,CAYA,SAASkwD,GAAY,G,IAAA,KAAE1/C,EAAI,KAAEnO,GAAR,EACnB,MAAM,MAAE6wB,GAAUqK,KACZN,EAAU/J,EAAM2I,cAAcrrB,GACpC,OAAKysB,GAEH,UAACtpC,IAAAA,CAAEP,KAAK,SAAStX,UAAU,iE,UACxBumB,IAAS46B,EAAQxzC,WAAW4Y,KAAS,UAACxK,OAAAA,CAAK/b,UAAU,c,UAAeumB,EAAK,QACzE46B,KAJgB,IAOvB,CAEA,SAAS2yB,GAAU,G,IAAA,KACjBvtD,EAAI,QACJwtD,EAAO,IACPpoD,EAAG,SACHqoD,EAAQ,OACRH,EAAM,cACNK,GAAgB,EAAK,MACrB7sD,GAPiB,EAsBjB,MAAMqtD,GACJ,UAAChrC,SAAAA,CACCtiC,KAAK,SACLkQ,KAAK,SACLs7D,eAAcjnD,EACd9I,SAAUqxD,EACVt2D,QAASo2D,EACTh0E,WAAWQ,EAAAA,EAAAA,IACT,qJACAmrB,EAAM,kBAAoB,yB,WAG5B,SAAC5P,OAAAA,CACCytB,eAAW,EACXxpC,WAAWQ,EAAAA,EAAAA,IACT,0DACCmrB,GAAO,c,UAGV,SAAC5P,OAAAA,CAAK/b,UAAU,mBAAmByK,MAAOspE,OAE5C,SAACh4D,OAAAA,CAAK/b,UAAU,0B,SAA2BumB,IAC1C2tD,GAAiB7sD,IAAS,SAACtL,OAAAA,CAAK/b,UAAU,mB,SAAoBqnB,KAC/D,SAAC4sD,GAAAA,CAAWtoD,IAAKA,EAAKuoD,cAAeA,OAIzC,OAAKL,GAEH,UAACxtD,GAAOA,C,WAIN,SAACovB,GAAcA,CAACx1C,SAAO,E,SACpBi0E,GAAgB,SAACn4D,OAAAA,CAAK/b,UAAU,e,SAAgB00E,IAAgBA,KAEnE,SAACh/B,GAAcA,CAACX,KAAK,OAAO/0C,UAAU,mB,SACnC6zE,OAVaa,CActB,CAGA,SAAST,GAAW,G,IAAA,IAAEtoD,EAAG,cAAEuoD,GAAP,EAClB,OACE,SAACn4D,OAAAA,CACCytB,eAAW,EACXxpC,WAAWQ,EAAAA,EAAAA,IACT,qEACAmrB,EAAM,aAAe,WACrBuoD,GAAiB,c,UAGnB,SAACn4D,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IACT,mFACAmrB,EAAM,WAAa,eAK7B,CC7de,SAASgpD,GAAY,G,IAAA,aAClCt1B,EAAY,iBACZu1B,EAAgB,UAChBC,EAAY,IAHsB,EAKlC,MAAM,MAAEz9B,EAAK,SAAEmK,GAAaE,MACtB,KAAE5sC,GAASyB,KACX,YAAEi6B,EAAW,QAAErd,EAAO,KAAEhP,GAAS0wD,EACjCj4D,EAAOy6B,EAAM6I,YACbC,EAAW9I,EAAM8I,SAEvB,GAAwB,iBAApBA,GAAUt+C,MACZ,OACE,SAACkzE,GAAAA,CACCvoE,OAAQ2zC,EACR0M,QAAS,IAAMrL,EAAS,CAAEn6C,KAAM,gBAAiBmF,OAAQ,SAK/D,IAAKoQ,EACH,OACE,SAAC9E,IAAAA,CAAE7X,UAAU,8C,SAA8C,iCAI/D,MAAM+0E,EACJ11B,GAAclgB,SAAS/8B,KAAMiwC,GAAMA,EAAEC,YAAY31B,OAASA,IAAO21B,YAAc,CAAC,EAC5E6f,EAAUzzB,GAAoB,EAAeA,IAA0C,KACvFs2C,EAAUzkC,EAAY9nC,IAAIkU,IAAS,KACnC6B,EAAKw2D,GAASv2D,cAAgB,KAI9Bw2D,EAAkB,MAANz2D,EAAa,KAAOW,GAAWX,GAC3C02D,GAAuC,IAA7BrgE,GAAMolC,mBAEtB,OACE,UAACt/B,MAAAA,CAAI3a,UAAU,gB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,uD,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,mC,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,wB,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAgC,eAChD,SAAC4uE,KAAAA,CAAG5uE,UAAU,yD,SAA0D2c,QAE1E,SAAC5c,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,+BACVuX,aAAW,cACX0E,MAAM,cACN2B,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,gBAAiBmF,OAAQ,O,UAEzD,SAAC49C,GAAAA,EAACA,CAACnqD,UAAU,gBAKhBkgD,GAA+B,QAAnBA,EAASt+C,QACpB,UAAC+Y,MAAAA,CAAI3a,UAAU,gE,WACb,UAAC+b,OAAAA,C,UAAK,cAC2B,UAAnBmkC,EAASt+C,MAAoB,MAAQ,KAAM,KACvD,SAACma,OAAAA,CAAK/b,UAAU,gB,SAAiB+wD,GAAS7Q,QAE3Ci1B,GAAaj1B,KACZ,SAACnkC,OAAAA,CAAK/b,UAAU,sC,SAAuCm1E,GAAaj1B,QAIzE80B,GACC,UAACn9D,IAAAA,CAAE7X,UAAU,kD,UACG,MAAbi1E,IACC,sB,WACE,SAACl5D,OAAAA,CACCytB,eAAW,EACXxpC,WAAWQ,EAAAA,EAAAA,IACT,sBACAy0E,EAAY,EAAI,iBAAmB,4BAKxB,IAAdA,GACC,SAACl5D,OAAAA,CAAK/b,UAAU,gB,SAAgB,YAEhC,UAAC+b,OAAAA,CAAK/b,WAAWQ,EAAAA,EAAAA,IAAGy0E,EAAY,GAAK,oB,WACnC,SAACl5D,OAAAA,CAAK/b,UAAU,oB,UAAqBS,EAAAA,EAAAA,GAAMK,KAAKC,IAAIk0E,GAAY,KAAW,KAC3E,UAACl5D,OAAAA,CAAK/b,UAAU,gB,UAAgB,MAC1Bi1E,EAAY,EAAI,QAAU,QAAQ,gBAM/CD,EAAQn4D,cACP,UAACd,OAAAA,CAAK/b,UAAU,wB,UACA,MAAbi1E,GAAqB,KAAK,WAASG,GAAUJ,EAAQn4D,oBAO5D,SAAChF,IAAAA,CAAE7X,WAAWQ,EAAAA,EAAAA,IAAG,cAAe0jB,EAAO,mBAAqB,yB,SACzDgP,EACG,mBACAhP,EACE,6CACA,iCAGgB,aAAzBkzB,EAAM4I,iBACL,SAACq1B,GAAAA,CAAcL,QAASH,EAAUzyE,KAAMib,GAAOA,EAAG4D,iBAAmBtE,IAAS,OAK/Eq4D,GAASpwE,OAAQ,UAACiT,IAAAA,CAAE7X,UAAU,gC,UAAgC,SAAOg1E,EAAQpwE,WAG/EswE,GAAWF,IACV,sB,WACE,SAACr6D,MAAAA,CAAI3a,UAAU,4C,SACZ,CAEG,CAAC,YAAag1E,EAAQx3D,qBACtB,CAAC,aAAcw3D,EAAQ72D,sBAEzBvT,IAAK,I,IAACsS,EAAO2G,GAAG,E,OAChB,UAAClJ,MAAAA,CAAgB3a,UAAU,sB,WACzB,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAiCkd,KACjD,SAACnB,OAAAA,CAAK/b,UAAU,4B,SAAmC,MAAN6jB,GAAapjB,EAAAA,EAAAA,GAAMojB,GAAM,OACtE,SAAC9H,OAAAA,CAAK/b,UAAU,gC,SAAgC,SAHxCkd,QAOd,UAACoyD,KAAAA,CAAGtvE,UAAU,uD,WACZ,SAACuvE,GAAKA,CACJryD,MAAM,iCACNxc,OAAQs0E,EAAQn2D,6BAElB,SAAC0wD,GAAKA,CAACryD,MAAM,yBAAyBxc,OAAQs0E,EAAQp2D,0BAK5D,UAAC0wD,KAAAA,CAAGtvE,UAAU,4C,UACXs1E,GAAQ1qE,IAAK,I,IAACsuD,EAAMh8C,GAAM,E,OACzB,SAACqyD,GAAKA,CAEJryD,MAAOA,EACPxc,OAAQyxD,EAAO,GAAG+G,WAClBzvB,QAAS2N,EAAM4I,iBAAmBkZ,GAH7BA,MAQT,SAACv+C,MAAAA,CAAI3a,UAAU,qB,UACb,SAACuvE,GAAKA,CACJryD,MAAM,MACNxc,OAAQyxD,EAAO,aACf1oB,QAAkC,QAAzB2N,EAAM4I,uBAKrB,UAACrlC,MAAAA,CAAI3a,UAAU,uB,WACb,SAACD,EAAAA,EAAMA,CACLb,QAAQ,UACRc,UAAU,kBACV4d,QAAS,IAAM2jC,EAAS,CAAEn6C,KAAM,wB,SACjC,iBAGA8tE,IACC,SAACn1E,EAAAA,EAAMA,CAACE,SAAO,EAACD,UAAU,kB,UACxB,SAACgoC,EAAAA,GAAIA,CAACC,GAAI,oBAAoBv/B,mBAAmBiU,K,SAAS,wBAMtE,CAEA,SAAS4yD,GAAM,G,IAAA,MACbryD,EAAK,OACLxc,EAAM,QACN+oC,GAHa,EAWb,OACE,UAAC9uB,MAAAA,CACC3a,WAAWQ,EAAAA,EAAAA,IACT,wDACYoL,IAAZ69B,GAAyB,OACzBA,GAAW,0B,WAGb,SAACimC,KAAAA,CAAG1vE,UAAU,wB,SAAyBkd,KACvC,SAACyyD,KAAAA,CAAG3vE,UAAU,Q,SAAmB,MAAVU,EAAiB,IAAGD,EAAAA,EAAAA,GAAMC,EAAQ,WAAa,QAG5E,CAMA,MAAM40E,GAAU,CACd,CAAC,MAAO,yBACR,CAAC,MAAO,oBAQV,SAASD,GAAc,G,IAAA,QAAEL,GAAF,EACrB,IAAKA,EAAS,OAAO,KACrB,GAAiC,MAA7BA,EAAQhkD,mBAA6BgkD,EAAQ92D,qBAAsB,CAGrE,MAAMq3D,GAoBWvvC,EApBSgvC,EAAQ92D,sBAoBJ+nB,EApB0B+uC,EAAQn4D,aAsB3D/b,KAAK4c,OAAOvS,KAAK/C,MAAM,GAAG69B,eAAmB96B,KAAK/C,MAAM,GAAG49B,gBAAoB,OADrE,MApBf,OACE,UAACnuB,IAAAA,CAAE7X,UAAU,gC,UAAgC,+BACdo1E,GAAUJ,EAAQ92D,sBACrC,MAATq3D,GAAiB,KAAKA,oBAG7B,CAaF,IAAqBvvC,EAAaC,EAZhC,OACE,SAACpuB,IAAAA,CAAE7X,UAAU,4C,SACY,iBAAtBg1E,EAAQh1D,UACL,uFACqB,oBAArBg1D,EAAQ1vC,SACN,0FACA,0EAGZ,CASA,SAAS6vC,GAAa5oE,GACpB,MAAMzH,EAAQ,CAEV,CAAC,MAAO,OACR,CAAC,MAAO,SAEVggD,QAAS,I,IAACoU,EAAM3yC,GAAK,EACrB,MAAM7lB,EAAS6L,EAAOikD,UAAU0I,GAChC,OAAiB,MAAVx4D,EAAiB,CAAC,GAAG6lB,MAAQ9lB,EAAAA,EAAAA,GAAMC,EAAQ,MAAQ,KAE5D,OAAOoE,EAAMX,OAAS,EAAI,GAAGW,EAAMM,KAAK,eAAiB,IAC3D,CAGA,SAAS0vE,GAAa,G,IAAA,OAAEvoE,EAAM,QAAEqgD,GAAV,EACpB,OACE,UAACjyC,MAAAA,CAAI3a,UAAU,4C,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,mC,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,wB,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,gC,SAAgC,kBAChD,SAAC4uE,KAAAA,CAAG5uE,UAAU,yD,SAA0DuM,EAAOoQ,WAEjF,SAAC5c,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,+BACVuX,aAAW,uBACX0E,MAAM,uBACN2B,QAASgvC,E,UAET,SAACzC,GAAAA,EAACA,CAACnqD,UAAU,gBAGM,MAAtBuM,EAAOkkD,cACN,UAAC54C,IAAAA,CAAE7X,UAAU,c,UAAc,YACf,KACV,SAAC+b,OAAAA,CAAK/b,UAAU,oB,UAAqBS,EAAAA,EAAAA,GAAM8L,EAAOkkD,YAAc,IAAQ,KAAU,SAIrFlkD,EAAOokD,cAAgBpkD,EAAOokD,eAAiBpkD,EAAOga,OACrD,SAAC1O,IAAAA,CAAE7X,UAAU,oC,SAAqCuM,EAAOokD,iBAIjE,CAGA,SAASykB,GAAUvzE,GACjB,OAAO,IAAIsJ,KAAK,GAAGtJ,cAChBia,mBAAmB,QAAS,CAAE6H,IAAK,UAAWH,MAAO,QAASC,KAAM,YACpEzV,QAAQ,IAAK,GAClB,CCzTA,MAAMwnE,GAAgB,iBAMtB,SAASC,KACP,IACE,OAAO7qD,OAAO2rB,aAAa,uBAAuB3oC,UAAW,CAC/D,CAAE,MAEA,OAAO,CACT,CACF,CAOA,SAAS8nE,KACP,MAAMC,EAJC,CAAE3/B,KAAMy/B,KAAiBG,IAAK,QAASC,WAAW,GAKzD,IACE,MAAMC,EAAa3tE,KAAKC,MAAM2iB,aAAaC,QAAQwqD,KAAkB,QACrE,OAAKM,GAAoC,iBAAfA,EACnB,CACL9/B,KAAiC,kBAApB8/B,EAAW9/B,KAAqB8/B,EAAW9/B,KAAO2/B,EAAM3/B,KACrE4/B,IAAK,CAAC,QAAS,MAAO,UAAU3oE,SAAS6oE,EAAWF,KAAOE,EAAWF,IAAMD,EAAMC,IAClFC,WAAoC,IAAzBC,EAAWD,WAJkCF,CAM5D,CAAE,MACA,OAAOA,CACT,CACF,CAwCA,MAAMI,GAAoE,CACxE,CAAEH,IAAK,QAAS14D,MAAO,QAAS84D,MAAOC,GAAAA,GACvC,CAAEL,IAAK,MAAO14D,MAAO,MAAO84D,MAAOE,GAAAA,GACnC,CAAEN,IAAK,SAAU14D,MAAO,SAAU84D,MAAOrrB,GAAAA,IAoB5B,SAASwrB,GAAS,G,IAAA,MAAEv5C,EAAK,IAAEw5C,EAAG,OAAE1wB,EAAM,SAAE9kC,EAAQ,aAAEy+B,GAAhC,EAC/B,MAAM,MAAEjI,GAAUqK,MACXkwB,EAAM0E,IAAWr4E,EAAAA,EAAAA,UAAwC,QAAzBo5C,EAAM4I,eAA2B,MAAQ,QLjG3E,SAA6Bp/B,GAClC,MAAM,MAAEw2B,EAAK,SAAEmK,GAAaE,MACtB,KAAEppC,GAAS++B,EAAMsJ,OAIjB41B,EAAa11D,EAAShW,IAAKyS,GAAOA,EAAGla,IAAIiC,KAAK,KAC9CmxE,GAAa1uE,EAAAA,EAAAA,SACjB,IAAOyuE,EAAaA,EAAW7xE,MAAM,KAAKmG,IAAIlG,QAAU,GACxD,CAAC4xE,KAMHr4E,EAAAA,EAAAA,WAAU,KACR,IAAKoa,GAAQA,EAAKlU,OAAS,GAA2B,IAAtBoyE,EAAWpyE,OAAc,OAEzD,IAAImgB,GAAa,EAqBjB,OApBAi9B,EAAS,CAAEn6C,KAAM,iBAEhB,WACC,IACE,MAAMyI,Q1FmW0EnE,E0FnWvB,CACvD5C,KAAM,CAAEuP,OAAMm+D,YAAaD,K1FmWzB7qE,EAAQoD,QAAUA,GAAQqF,KAAwE,CACtGnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,sCACFsI,EACH7C,QAAS,CACL,eAAgB,sBACb6C,EAAQ7C,Y0F9Wf,GAAIyb,EAAY,OACZzU,EAASC,KACXyxC,EAAS,CAAEn6C,KAAM,mBAAoB4lB,UAAWnd,EAASC,OAEzDyxC,EAAS,CAAEn6C,KAAM,cAAe8c,KAAM,2CAE1C,CAAE,MACKI,GACHi9B,EAAS,CAAEn6C,KAAM,cAAe8c,KAAM,iCAE1C,C1FsV2C,IAAuCxY,C0FrVnF,EAhBA,GAkBM,KACL4Y,GAAa,IAEd,CAACjM,EAAMk+D,EAAYh1B,GACxB,CK0DEk1B,CAAoB71D,GAKpB,MAAMg0D,EAAmBvkC,GAAkB,GAIrCqmC,GAAaj2C,EAAAA,EAAAA,SAAO,GAS1B,IARAxiC,EAAAA,EAAAA,WAAU,KACJy4E,EAAW9yC,QACb8yC,EAAW9yC,SAAU,EAGvB8hB,EAAO9hB,SAASwjB,UACf,CAACxqB,EAAMoZ,KAAM0P,KAEX9oB,EAAMoZ,KAAM,CACf,MAAMy0B,EAAO0F,GAAY/4B,EAAM4I,eAAgB5I,EAAMta,SAAU,UACzDvW,EAAOupD,GAAgB14B,EAAM4I,gBAI7Bp7B,GAAcgzC,EAAAA,GAAAA,IAAmBxgB,EAAM4I,iBAAsC,SAAnB5I,EAAMta,SAChE65C,EAAmB,wCAAwC5G,GAAe34B,EAAM4I,kBACtF,OACE,UAAC42B,QAAAA,CACCr/D,aAAW,uBACXvX,UAAU,6E,WAEV,SAAC62E,GAAAA,CACC35D,MAAM,sBACNU,QAAS,IAAMw4D,EAAI,CAAEpgC,MAAM,IAC3Bh2C,UAAU,O,UAEV,SAAC82E,GAAAA,EAAYA,CAAC92E,UAAU,kBAEzB+1E,GAAKnrE,IAAK,I,IAAA,IAAEgrE,EAAG,MAAE14D,EAAK,MAAE84D,GAAO,E,OAC9B,SAACa,GAAAA,CAAqB35D,MAAOA,EAAOU,QAAS,IAAMw4D,EAAI,CAAEpgC,MAAM,EAAM4/B,Q,UACnE,SAACI,EAAAA,CAAMh2E,UAAU,iBADF41E,MAInB,SAACj7D,MAAAA,CAAI3a,UAAU,YACf,UAAC0pC,SAAAA,CACCtiC,KAAK,SACLmQ,aACEqN,EAAc,0BAA0B+xD,IAAqB,wBAE/D16D,MAAO2I,EAAc+xD,EAAmB,aACxC/4D,QAAS,IAAMw4D,EAAI,CAAEpgC,MAAM,IAC3Bh2C,WAAWQ,EAAAA,EAAAA,IACT,yFACAokB,EAAc,yCAA2C,sB,WAG3D,SAAC7I,OAAAA,CACC/b,WAAWQ,EAAAA,EAAAA,IAET+lB,EAAKpiB,OAAS,EAAI,cAAgB,gBAClC,cACAygB,EAAc,yBAA2B,gB,SAG1C2B,IAEF3B,IAAe,SAAC7I,OAAAA,CAAK/b,UAAU,iC,SAAiC,UACjE,SAAC+b,OAAAA,CAAK/b,UAAU,gE,SACbyqE,EAAKgE,QAER,SAAC1yD,OAAAA,CAAK/b,UAAU,8BAA8ByK,MAAO,CAAEi6B,WAAY+lC,EAAK2F,gBACxE,SAACr0D,OAAAA,CAAK/b,UAAU,gE,SACbyqE,EAAKp+C,aAKhB,CAEA,MAAM0qD,EAAahB,GAAKryE,UAAW,I,IAAA,IAAEkyE,GAAK,E,OAAKA,IAAQh5C,EAAMg5C,MAE7D,OAIE,UAACgB,QAAAA,CACCr/D,aAAW,WACXvX,UAAU,0J,WAEV,SAACkyE,GAAkBA,CAACP,KAAMA,EAAMD,OAAQ2E,EAASlE,YAAa,IAAMiE,EAAI,CAAEpgC,MAAM,OAEhF,SAACr7B,MAAAA,CACCrD,KAAK,UACLC,aAAW,YACXvX,UAAU,6DACVgqD,UAAY75C,IACV,MAAM1M,EAAImsE,GAAcz/D,EAAEvI,IAAKmvE,EAAYhB,GAAK5xE,QACvC,MAALV,IACJ0M,EAAEm/B,iBACF8mC,EAAI,CAAER,IAAKG,GAAKtyE,GAAGmyE,MACnBzlE,EAAEsiE,cAAcC,iBAA8B,gBAAgBjvE,IAAIkvE,U,SAGnEoD,GAAKnrE,IAAK,I,IAAA,IAAEgrE,EAAG,MAAE14D,EAAK,MAAE84D,GAAO,EAC9B,MAAMvsC,EAAU7M,EAAMg5C,MAAQA,EAC9B,OACE,UAAClsC,SAAAA,CAECtiC,KAAK,SACLkQ,KAAK,MACLnU,GAAI,gBAAgByyE,IACpBoB,gBAAevtC,EACfwtC,gBAAc,kBACdttB,SAAUlgB,EAAU,GAAK,EACzB7rB,QAAS,IAAMw4D,EAAI,CAAER,QACrB51E,WAAWQ,EAAAA,EAAAA,IACT,8EACAipC,EACI,4CACA,8E,WAGN,SAACusC,EAAAA,CAAMh2E,UAAU,WACR,QAAR41E,GAA2C,iBAA1Bx+B,EAAM8I,UAAUt+C,MAC9Bw1C,EAAM8I,SAASvjC,KACP,QAARi5D,GAAiBx+B,EAAM6I,YACrB,OAAO7I,EAAM6I,cACb/iC,IApBD04D,QA0Bb,UAACj7D,MAAAA,CACCxX,GAAG,kBACHmU,KAAK,WACL4/D,kBAAiB,gBAAgBt6C,EAAMg5C,MACvC51E,UAAU,iC,UAEK,UAAd48B,EAAMg5C,MACL,SAACpC,GAAaA,CAACC,KAAM72C,EAAMi5C,UAAWnC,OAASmC,GAAcO,EAAI,CAAEP,gBAEtD,QAAdj5C,EAAMg5C,MACL,SAACjB,GAAWA,CACVt1B,aAAcA,EACdu1B,iBAAkBA,EAClBC,UAAWj0D,IAGA,WAAdgc,EAAMg5C,MAAoB,SAACjH,GAAcA,CAAAA,QAIlD,CAEA,SAASkI,GAAW,G,IAAA,MAClB35D,EAAK,QACLU,EAAO,UACP5d,EAAS,SACTsV,GAJkB,EAWlB,OACE,SAACo0B,SAAAA,CACCtiC,KAAK,SACLmQ,aAAY2F,EACZjB,MAAOiB,EACPU,QAASA,EACT5d,WAAWQ,EAAAA,EAAAA,IACT,qHACAR,G,SAGDsV,GAGP,CC1SA,SAAS6hE,KACP,MAAM,MAAE//B,EAAK,SAAEmK,GAAaE,KACtBiE,GAASjlB,EAAAA,EAAAA,QAAsB,OAC/B,MAAE7D,EAAK,IAAEw5C,EAAG,OAAEgB,GD0Df,WACL,MAAOx6C,EAAO8D,IAAY1iC,EAAAA,EAAAA,UAAwB03E,KAC3CjsC,EAAS4tC,IAAcr5E,EAAAA,EAAAA,WAAS,IAEvCC,EAAAA,EAAAA,WAAU,KACR,GAAKwrC,EACL,IACE1e,aAAausD,QAAQ9B,GAAertE,KAAKY,UAAU6zB,GACrD,CAAE,MAEF,GACC,CAACA,EAAO6M,IAEX,MAAM2sC,GAAMvgE,EAAAA,EAAAA,aAAa0hE,IACvBF,GAAW,GACX32C,EAAUylC,IAAS,IAAKA,KAAQoR,MAC/B,IAIGH,GAASvhE,EAAAA,EAAAA,aACZ+/D,GAAqBQ,EAAIX,KAAkB,CAAEz/B,MAAM,EAAM4/B,OAAQ,CAAEA,QACpE,CAACQ,IAGH,MAAO,CAAEx5C,QAAOw5C,MAAKgB,SACvB,CCpFiCI,IAEzB,SAAE52D,GAAaoS,GAAiBokB,EAAM+I,aAAc/I,EAAMxlB,kBACxDhR,SAAU6kC,GAAgBlyB,GAChC6jB,EAAM+I,aACN/I,EAAMxlB,gBACN8vB,GAAetK,EAAO,SAEhBlY,QAASu4C,GAAwBznC,KAKnC0nC,GAAoB7hE,EAAAA,EAAAA,aACvBtJ,IACCg1C,EAAS,CAAEn6C,KAAM,gBAAiBmF,WAC9BA,GAAQ6qE,EAAO,QAErB,CAAC71B,EAAU61B,IAGPO,GAAoB9hE,EAAAA,EAAAA,aAAY,IAAMuhE,EAAO,UAAW,CAACA,IAE/D,OAGE,UAAClxC,UAAAA,CAAQlmC,UAAU,sG,WAEjB,SAAC0wC,KAAAA,CAAG1wC,UAAU,U,SAAU,WAKxB,UAAC2a,MAAAA,CAAI3a,UAAU,+B,WAIb,UAAC2a,MAAAA,CAAI3a,UAAU,+B,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,0C,WACb,SAAC48D,GAAOA,CAACC,cAAe6a,EAAmBhyB,OAAQA,KACnD,SAAC8E,GAAUA,CAAC9E,OAAQA,EAAQ+E,cAAektB,OAG5CvgC,EAAMoJ,aAAavtC,OAClB,SAACuyC,GAAkBA,CACjB5kC,SAAUA,EACV6kC,YAAaA,EACbpG,aAAco4B,EACd/xB,OAAQA,QAMd,SAACywB,GAAQA,CACPv5C,MAAOA,EACPw5C,IAAKA,EACL1wB,OAAQA,EACR9kC,SAAUA,EACVy+B,aAAco4B,QAIlB,SAAC/K,GAAWA,CAAAA,KAGlB,C,uDC/EA,MAAMkL,GAAc,IAAI9wE,IACX+wE,GAAiB,IAuCvBz5E,eAAe05E,GACpBC,EACAhzE,G,IACAnD,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA4B,QAE5B,MAAM88B,EAAU,GAAGq5C,KAASn2E,KAASmD,IAC/BgtB,EAAS6lD,GAAYnvE,IAAIi2B,GAC/B,GAAI3M,GAAUA,EAAOimD,OAAS7sE,KAAKoU,MAAO,OAAOwS,EAAO3uB,IACxD,IACE,MAAM4J,EAAkB,WAAVpL,EAAqB,gBAAkB,GAC/Cq2E,QAAa35E,MAAM,sBAAsByG,aAAgBiI,KAC/D,IAAKirE,EAAK15E,GAAI,OAAO,KACrB,MAAM6E,SAAa60E,EAAKz5E,QAAQ4E,IAEhC,OADAw0E,GAAYnuE,IAAIi1B,EAAS,CAAEt7B,MAAK40E,OAAQ7sE,KAAKoU,MAAQs4D,KAC9Cz0E,CACT,CAAE,MACA,OAAO,IACT,CACF,CAQOhF,eAAe85E,KACpB,IACE,MAAMD,QAAa35E,MAAM,4BACzB,OAAK25E,EAAK15E,SACI05E,EAAKz5E,OADE,IAEvB,CAAE,MACA,OAAO,IACT,CACF,CCnEe,SAAS25E,GAAY,G,IAAA,MAAEC,EAAK,QAAExrB,GAAT,EAClC,MAAOxpD,EAAKi1E,IAAUr6E,EAAAA,EAAAA,UAAwB,OACvCkmB,EAAMG,IAAWrmB,EAAAA,EAAAA,WAAS,GAsBjC,OApBAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAMhB,OALA2vE,KAAuBx5E,KAAM2zC,IACvB9pC,IACA8pC,EAAGgmC,EAAOhmC,EAAEjvC,KACXihB,GAAQ,MAER,KACL9b,GAAY,IAEb,KAEHtK,EAAAA,EAAAA,WAAU,KACR,MAAM0wD,EAAWx+C,IACD,WAAVA,EAAEvI,KAAkBglD,KAG1B,OADAhiC,OAAOpY,iBAAiB,UAAWm8C,GAC5B,IAAM/jC,OAAOnX,oBAAoB,UAAWk7C,IAClD,CAAC/B,KAGF,UAACjyC,MAAAA,CACC3a,UAAU,wFACVsX,KAAK,SACLC,aAAY6gE,E,WAEZ,UAACz9D,MAAAA,CAAI3a,UAAU,qE,WACb,SAAC+b,OAAAA,CAAK/b,UAAU,sB,SAAuBo4E,KACvC,UAACr4E,EAAAA,EAAMA,CACLN,KAAK,OACLP,QAAQ,QACRc,UAAU,+BACV4d,QAASgvC,E,WAET,SAACzC,GAAAA,EAACA,CAACnqD,UAAU,YACb,SAAC+b,OAAAA,CAAK/b,UAAU,U,SAAU,kBAG7BoD,IACC,SAACk1E,QAAAA,CACC99B,IAAKp3C,EACLpD,UAAU,2DACVu4E,UAAQ,EACRC,UAAQ,EACRC,MAAI,EACJC,OAAK,EACLC,aAAW,KAGbv1E,IAAQ8gB,IAAQ,SAACnI,OAAAA,CAAK/b,UAAU,wB,SAAwB,gBACzDkkB,IAAQ,SAACnI,OAAAA,CAAK/b,UAAU,wB,SAAwB,mCAGvD,C,eCvCe,SAAS44E,GAAS,G,IAAA,MAC/BC,EAAK,KACL9zE,EAAI,UACJ+zE,EAAS,aACTj5D,EAAY,gBACZk5D,EAAe,OACfC,EAAM,MACN/kC,EAAK,OACLglC,EAAM,SACNC,EAAQ,kBACRC,GAV+B,EAY/B,MAAMv/C,EAAQ94B,KAAKkX,IAAI,EAAG6gE,EAAM9kB,QAAQhvD,IAClC4gC,EAAUkzC,EAAM10E,OAAS,EACzBi1E,EAAOz1E,GAAiB,IAAO7C,KAAKkX,IAAI,EAAG6gE,EAAM9kB,QAAQpwD,IAAO7C,KAAKkX,IAAI,EAAG2tB,GAAvD,IACrB0zC,EAAeP,EAAU72E,OAAQ0B,GAAMk1E,EAAM5rE,SAAStJ,IAQ5D,OANA1F,EAAAA,EAAAA,WAAU,KACR,IAAK+6E,GAAU/kC,EAAO,OACtB,MAAMxnB,EAAI5tB,YAAY,IAAMo6E,EAAOJ,EAAMj/C,GAAS+L,EAAU,EAAI/L,EAAQ,IAzCzD,KA0Cf,MAAO,IAAM96B,cAAc2tB,IAC1B,CAACusD,EAAQ/kC,EAAOra,EAAO+L,EAASkzC,EAAOI,KAGxC,UAACt+D,MAAAA,CAAI3a,UAAU,2E,WACb,SAACD,EAAAA,EAAMA,CACLN,KAAK,OACLP,QAAQ,UACRqY,aAAYyhE,EAAS,WAAa,WAClCp7D,QAAS,IAAMs7D,GAAUF,G,SAExBA,GAAU/kC,GACT,SAACqlC,GAAAA,EAAOA,CAACt5E,UAAU,wBACjBg5E,GACF,SAAC/K,GAAAA,EAAKA,CAACjuE,UAAU,YAEjB,SAACkuE,GAAAA,EAAIA,CAACluE,UAAU,cAGpB,SAAC2a,MAAAA,CAAI3a,UAAU,6C,SAA8C+E,KAC7D,UAAC4V,MAAAA,CAAI3a,UAAU,4B,UACZ+4E,GAAmBl5D,GAAgBg5D,EAAM5rE,SAAS4S,EAAa,MAC9D,SAAClF,MAAAA,CACC3a,UAAU,iDACVyK,MAAO,CACLk6B,KAAMy0C,EAAIv5D,EAAa,IACvB1I,MAAO,QAAQiiE,EAAIv5D,EAAa,SAASu5D,EAAIv5D,EAAa,QAE5D5D,MAAM,iBAGTo9D,EAAazuE,IAAKjH,IACjB,SAACgX,MAAAA,CAEC3a,UAAU,yCACVyK,MAAO,CAAEk6B,KAAMy0C,EAAIz1E,IACnBsY,MAAO,UAAUtY,KAHZA,KAMT,SAACioE,GAAMA,CACLh4D,IAAK,EACLoE,IAAK2tB,EACL0oC,KAAM,EACN3jE,MAAO,CAACkvB,GACRlY,cAAgB,I,IAACje,GAAE,E,OAAKw1E,EAAOJ,EAAMp1E,KACrC8T,aAAW,SAEZ8hE,EAAazuE,IAAKjH,IACjB,SAACoY,OAAAA,CAEC/b,UAAU,yEACVyK,MAAO,CAAEk6B,KAAMy0C,EAAIz1E,I,SAElBA,GAJIA,QAQX,UAACgX,MAAAA,CAAI3a,UAAU,0B,WACb,SAACu5E,GAAAA,EAAMA,CACLp2E,GAAG,kBACHq2E,QAAST,EACTU,gBAAiBN,KAEnB,SAACh9B,GAAKA,CAACu9B,QAAQ,kBAAkB15E,UAAU,4B,SAA4B,2BAM/E,CClEO,MAID25E,GAA8C,CAClD,CAAC,EAAK,CAAC,GAAI,GAAI,KACf,CAAC,IAAM,CAAC,GAAI,GAAI,MAChB,CAAC,GAAK,CAAC,GAAI,IAAK,MAChB,CAAC,IAAM,CAAC,IAAK,IAAK,MAClB,CAAC,IAAM,CAAC,IAAK,IAAK,MAClB,CAAC,EAAK,CAAC,IAAK,IAAK,MAwBZ,SAASC,GACdxe,EACA7xB,GAEA,MAAMrJ,EAASk7B,EAAM,GACrB,IAAK12D,OAAOi4B,SAASuD,GAEnB,YADAqJ,EAAM,GAAK,GAGb,MAAOlrC,EAAG4pB,EAAGvlB,GA9BR,SAA4Bw9B,GACjC,MAAMzT,EAAI3rB,KAAK8S,IACb,EACA9S,KAAKkX,IAAI,GAAIkoB,IAhBgB,IAgBe25C,KAE9C,IAAK,IAAIp2E,EAAI,EAAGA,EAAIk2E,GAAMx1E,OAAS,EAAGV,IAAK,CACzC,MAAOq2E,EAAIC,GAAMJ,GAAMl2E,IAChBu2E,EAAIC,GAAMN,GAAMl2E,EAAI,GAC3B,GAAIgpB,GAAKutD,EAAI,CACX,MAAM3wE,GAAKojB,EAAIqtD,IAAOE,EAAKF,GAC3B,MAAO,CACLh5E,KAAK4c,MAAMq8D,EAAG,GAAK1wE,GAAK4wE,EAAG,GAAKF,EAAG,KACnCj5E,KAAK4c,MAAMq8D,EAAG,GAAK1wE,GAAK4wE,EAAG,GAAKF,EAAG,KACnCj5E,KAAK4c,MAAMq8D,EAAG,GAAK1wE,GAAK4wE,EAAG,GAAKF,EAAG,KAEvC,CACF,CACA,OAAOJ,GAAMA,GAAMx1E,OAAS,GAAG,EACjC,CAYoB+1E,CAAmBh6C,GACrCqJ,EAAM,GAAKlrC,EACXkrC,EAAM,GAAKthB,EACXshB,EAAM,GAAK7mC,EACX6mC,EAAM,GAAK,GACb,CAmCO,SAAS4wC,GAAkB/tD,GAChC,MAAMguD,EAAYhuD,EAAIiuD,aAAap4E,OAAQgmB,GAAMA,EAAEmyD,WACnD,GAAyB,IAArBA,EAAUj2E,OAAc,OAAO,KACnC,MAAMwhC,EAAU7kC,KAAKkX,OAAOoiE,EAAUxvE,IAAKqd,GAAMA,EAAEqyD,iBACnD,MAAO,CAAC30C,EAASA,EAAUvZ,EAAImuD,QACjC,CAyCO,SAASzB,GAAU1sD,GACxB,OAAOA,EAAIysD,MAAM52E,OAAQ0B,GAAMA,EAAE62E,cAAc5vE,IAAKjH,GAAMA,EAAEoB,KAC9D,CC/IA,MAMM01E,GAAY,IAAI3zE,IAEtB,SAAS4zE,GAAU7B,EAAiB9zE,EAAcpB,GAChD,OAAO7C,KAAKC,IAAI83E,EAAM9kB,QAAQpwD,GAAKk1E,EAAM9kB,QAAQhvD,KATrC,GASwD8zE,EAAM5rE,SAAStJ,EACrF,CAQA,SAASg3E,GAAO/4E,EAA2BmD,GACzC,MAAO,iBAAiBnD,KAASmD,GACnC,CAee,SAAS61E,GAAmB,G,IAAA,MACzC7C,EAAK,MACLn2E,EAAQ,QAAO,MACfi3E,EAAK,KACL9zE,EAAI,SACJuqD,EAAQ,UACRurB,GAAY,EAAK,QACjBC,GAPyC,EASzC,MAAQl3C,QAASjiC,IAAUo5E,EAAAA,GAAAA,OAGpBC,EAAOC,IAAYj9E,EAAAA,EAAAA,UACxB,KAAO,CAAE4D,QAAO0nE,KAAM,IAAIxiE,OAEtBwiE,EAAO0R,EAAMp5E,QAAUA,EAAQo5E,EAAM1R,KAAOmR,GAG5ChU,GAAUhmC,EAAAA,EAAAA,QAAO,IAAI58B,MAEpBq3E,EAAKC,IAAUn9E,EAAAA,EAAAA,UAAsB,IAAM,IAAI6F,KAChDu3E,GAAU36C,EAAAA,EAAAA,QAAO6oC,GACvB8R,EAAQx3C,QAAU0lC,EAClB,MAAM+R,GAAU56C,EAAAA,EAAAA,QAAO17B,GACvBs2E,EAAQz3C,QAAU7+B,EAClB,MAAMu2E,EAAW33E,GAAck1E,EAAM5rE,SAAStJ,KAAO+2E,GAAU7B,EAAO9zE,EAAMpB,IAAMu3E,EAAIp3E,IAAIH,IAiF1F,OA/EA1F,EAAAA,EAAAA,WAAU,KACH48E,GAAWM,EAAO,IAAIt3E,MAC1B,CAACg3E,KAIJ58E,EAAAA,EAAAA,WAAU,KACRg9E,EAAS,CAAEr5E,QAAO0nE,KAAM,IAAIxiE,MAC5B2/D,EAAQ7iC,QAAQhW,QAChB,MAAMnB,EAAI5tB,YAAY,KACpBo8E,EAAS,CAAEr5E,QAAO0nE,KAAM,IAAIxiE,MAC5B2/D,EAAQ7iC,QAAQhW,SACfiqD,IACH,MAAO,IAAM/4E,cAAc2tB,IAC1B,CAACsrD,EAAOn2E,KAEX3D,EAAAA,EAAAA,WAAU,KACR,MAAMs9E,EAAU1C,EAAM52E,OAAQ0B,GAAM23E,EAAQ33E,KAAO2lE,EAAKxlE,IAAIH,KAAO8iE,EAAQ7iC,QAAQ9/B,IAAIH,IACvF,GAAuB,IAAnB43E,EAAQp3E,OAAc,OAC1B,IAAIoE,GAAY,EAehB,OAdA0I,QAAQ2hB,IACN2oD,EAAQ3wE,IAAIxM,MAAOuF,GAAM,CAACA,QAASm0E,GAAuBC,EAAOp0E,EAAG/B,MACpElD,KAAM88E,IACN,IAAIjzE,EAAJ,CACA,IAAK,MAAO5E,EAAGP,KAAQo4E,EACjBp4E,GAAK2jE,EAAAA,GAAAA,IAAiB3jE,EAAKw2E,IAC1BnT,EAAQ7iC,QAAQl+B,IAAI/B,GAE3Bs3E,EAAU9U,IACR,GAAIA,EAAIvkE,QAAUA,EAAO,OAAOukE,EAChC,MAAM1gE,ED0BP,SACL0gE,EACAqV,GAEA,MAAMC,EAAcD,EAAMv5E,OACvB4V,GAA+C,OAATA,EAAE,IAAesuD,EAAI19D,IAAIoP,EAAE,MAAQA,EAAE,IAE9E,GAA2B,IAAvB4jE,EAAYt3E,OAAc,OAAOgiE,EACrC,MAAM1gE,EAAQ,IAAIqB,IAAIq/D,GACtB,IAAK,MAAOxiE,EAAGP,KAAQq4E,EAAah2E,EAAMgE,IAAI9F,EAAGP,GACjD,OAAOqC,CACT,CCrCsBi2E,CAAYvV,EAAImD,KAAMkS,GACpC,OAAO/1E,IAAU0gE,EAAImD,KAAOnD,EAAM,CAAEvkE,QAAO0nE,KAAM7jE,IAR9B,IAWhB,KACL8C,GAAY,IAEb,CAACwvE,EAAOn2E,EAAOi3E,EAAO9zE,EAAMukE,EAAM4R,KAErCj9E,EAAAA,EAAAA,WAAU,KACR,IAAK48E,IAAcC,EAAS,OAC5B,MAAM5wD,EAAOiC,GAAY,0BAA2B,CAAE0sD,MAAOA,EAAM10E,SACnE,IAAI4Z,GAAU,EACd,MAAMqxC,EAAM,KACV,MAAMxkD,EAAMjJ,GAAOipD,SACnB,IAAKhgD,EAAK,OAEV,MAAM3G,EAAS40E,EAAM52E,OAAQ0B,IAC3B,GAAI8iE,EAAQ7iC,QAAQ9/B,IAAIH,GAAI,OAAO,EACnC,MAAMR,EAAKw3E,GAAO/4E,EAAO+B,GACzB,QAASiH,EAAI8yD,UAAUv6D,IAAOyH,EAAIg2D,eAAez9D,KAChDgB,OACH22E,EAAQ72E,GACH8Z,GAAW9Z,IAAW40E,EAAM10E,SAC/B4Z,GAAU,EACVmM,KAIFixD,EAAQhV,IACN,MAAMwV,EAAgBh4E,GAAc+2E,GAAU7B,EAAOwC,EAAQz3C,QAASjgC,IAAMwiE,EAAIriE,IAAIH,GAIpF,GAHck1E,EAAMx1E,KACjBM,GAAMg4E,EAAah4E,KAAOy3E,EAAQx3C,QAAQ9/B,IAAIH,KAAO8iE,EAAQ7iC,QAAQ9/B,IAAIH,MAE9DiH,EAAIizD,iBAAkB,OAAOsI,EAC3C,MAAMkB,EAlHd,SAAyBwR,EAAiB9zE,GACxC,MAAMtB,EAAI3C,KAAKkX,IAAI,EAAG6gE,EAAM9kB,QAAQhvD,IACpC,MAAO,IAAI8zE,EAAMx2E,MAAMoB,EAAI,MAAOo1E,EAAMx2E,MAAM,EAAGoB,GACnD,CA+GyBm4E,CAAgB/C,EAAOwC,EAAQz3C,SAC7C3hC,OAAQ0B,IAAOg4E,EAAah4E,IAC5BtB,MAAM,EA7HG,GA8HZ,OAA2B,IAApBglE,EAASljE,OAAegiE,EAAM,IAAItiE,IAAI,IAAIsiE,KAAQkB,OAG7DjY,IACA,MAAM3iC,EAAI5tB,YAAYuwD,EApIL,KAqIjB,MAAO,KACLtwD,cAAc2tB,GACT1O,GAASmM,EAAK,eAEpB,CAAC2wD,EAAWC,EAASn5E,EAAOk3E,EAAOj3E,KAGpC,qB,SACG,IAAI0nE,EAAKt/D,WACP/H,OAAQ,I,IAAC0B,GAAE,E,OAAK23E,EAAQ33E,KACxBiH,IAAK,I,IAACjH,EAAGP,GAAI,E,OACZ,SAACipD,GAAAA,GAAMA,CAILlpD,GAAIw3E,GAAO/4E,EAAO+B,GAClByD,KAAK,SACLhE,IAAK,SAASA,mBAAqBxB,KAAS+B,IAC5C4D,SAAU,I,UAEV,SAAC+kD,GAAAA,GAAKA,CACJnpD,GAAI,sBAAsBvB,KAAS+B,IACnCyD,KAAK,SACLkoD,SAAUA,EACV/C,MAAO,CACL,iBAAkB5oD,IAAMoB,EAAO,EAAI,EACnC,4BAA6B,CAAE0pB,SAAU,GACzC,uBAAwB,MAbvB,GAAG7sB,KAAS+B,QAoB7B,CCxLA,MAAMk4E,GAAQ,CAAE/4B,SAAU,MAAOC,UAAW,KAAM58C,KAAM,MAGlD21E,GAAyC,CAAE10E,KAAM,oBAAqB+3B,SAAU,IAEhF48C,GAAc,iBAEL,SAASC,KACtB,MAAM,IAAE5vD,EAAG,MAAE6nB,EAAK,KAAEgoC,GLzBf,WACL,MAAO7vD,EAAK8vD,IAAUl+E,EAAAA,EAAAA,UAAkC,OACjDi2C,EAAOqd,IAAYtzD,EAAAA,EAAAA,WAAS,IAC5Bi+E,EAAME,IAAWn+E,EAAAA,EAAAA,WAAS,GA0BjC,OAxBAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAkBhB,OAjBAjK,MAAM,uBACHI,KAAKN,MAAOC,IACX,GAAIkK,EAAW,OACf,GAAiB,MAAblK,EAAE8K,OAEJ,YADAgzE,GAAQ,GAGV,IAAK99E,EAAEE,GAAI,MAAM,IAAI2K,MAAM7K,EAAE2T,YAC7B,MAAMlC,QAAczR,EAAEG,OACjB+J,GAAW2zE,EAAOpsE,KAExB2U,MAAM,KACAlc,GAAW4zE,GAAQ,KAEzBz3D,QAAQ,KACFnc,GAAW+oD,GAAS,KAEtB,KACL/oD,GAAY,IAEb,IAEI,CAAE6jB,MAAK6nB,QAAOgoC,OACvB,CKL+BG,IACrBl9C,QAASmgB,GAAiBrP,KAC5BqsC,EL8CD,WACL,MAAOA,EAAMC,IAAWt+E,EAAAA,EAAAA,UAAmC,MAU3D,OATAC,EAAAA,EAAAA,WAAU,KACR,IAAIsK,GAAY,EAIhB,OAHA2vE,KAAuBx5E,KAAM2zC,IACtB9pC,GAAW+zE,EAAQjqC,KAEnB,KACL9pC,GAAY,IAEb,IACI8zE,CACT,CK1DeE,IACNC,EAAUC,IAAez+E,EAAAA,EAAAA,WAAS,GACnCgJ,GAAaW,EAAAA,GAAAA,GAAoB,aACjC+0E,GAAQ70E,EAAAA,EAAAA,SAAQ,KAAMd,EAAAA,GAAAA,GAAgBC,GAAa,CAACA,KACnD+xE,EAAiB4D,IAAsB3+E,EAAAA,EAAAA,WAAS,GACjD66E,GAAQhxE,EAAAA,EAAAA,SACZ,IAAOukB,EFsIJ,SAAwBA,EAAuB2sD,GACpD,OAAIA,EAAwB3sD,EAAIysD,MAAMjuE,IAAKjH,GAAMA,EAAEoB,MAC5C+zE,GAAU1sD,EACnB,CEzIiBwwD,CAAexwD,EAAK2sD,GAAmB,GACpD,CAAC3sD,EAAK2sD,IAEFD,GAAYjxE,EAAAA,EAAAA,SAAQ,IAAOukB,EAAMywD,GAAazwD,GAAO,GAAK,CAACA,IAE3DxqB,EAAQm3E,EAAkB,QAAU,UACnC+D,EAAWC,IAAgB/+E,EAAAA,EAAAA,UAAwB,OACnDg7E,EAAQgE,IAAah/E,EAAAA,EAAAA,WAAS,IAE9BgE,EAAOi7E,IAAYj/E,EAAAA,EAAAA,UAAS,GAC7Bk/E,GAAYrnE,EAAAA,EAAAA,aAAa8V,IAEzBA,GAAKsxD,EAAS,GAClBD,EAAUrxD,IACT,KACIuU,EAAQi9C,IAAan/E,EAAAA,EAAAA,UAAwB,MAC9Co/E,GAAc38C,EAAAA,EAAAA,QAAO,GAIrB48C,EAAYrE,GAAUh3E,EAAQ62E,EAAM10E,OACpCo3E,EAAUuB,GAAahE,EAAUA,EAAU30E,OAAS,IAAM00E,EAAMA,EAAM10E,OAAS,IAAM,EACrFY,EAAO8zE,EAAM10E,OAAS,EF2HvB,SAA8B00E,EAAiB9zE,GACpD,OAAO8zE,EAAM73C,OAAO,CAAC1+B,EAAOqB,IAAO7C,KAAKC,IAAI4C,EAAIoB,GAAQjE,KAAKC,IAAIuB,EAAQyC,GAAQpB,EAAIrB,EACvF,CE7HkCg7E,CAAqBzE,EAAO0C,GAAWA,EAEjEgC,GAAS1nE,EAAAA,EAAAA,aACbzX,MAAO+R,IACL,MAAMmd,EAAKniB,KAAKoU,MAChB,GAAI+N,EAAK8vD,EAAYx5C,QAzCH,IAyC8B,OAEhD,GADAw5C,EAAYx5C,QAAUtW,GACjBlB,EAAK,OACV,MAAMhpB,QAAY00E,GAAuB1rD,EAAIjpB,GAAI4B,EAAMnD,GACvD,GAAKwB,EACL,IACE,MAAM0sD,QAAUjN,EAAAA,GAAAA,IACdz/C,EACA,CAAE0/C,SAAU3yC,EAAEy7C,OAAO/iC,IAAKk6B,UAAW5yC,EAAEy7C,OAAOpsB,KAC9CrvB,EAAE02C,OAAOsX,WAEXgf,EAAUrtB,EAAE3rD,OAAS,GAAKO,OAAOi4B,SAASmzB,EAAE,IAAMA,EAAE,GAAK,KAC3D,CAAE,MACAqtB,EAAU,KACZ,GAEF,CAACp4E,EAAMqnB,EAAKxqB,IAGd,OAAIqyC,GACK,SAACt5B,MAAAA,CAAI3a,UAAU,oC,SAAoC,4BAExDi8E,IAAS7vD,GAET,SAACzR,MAAAA,CAAI3a,UAAU,oC,SAAoC,+CASrD,UAACkmC,UAAAA,CAAQlmC,UAAU,qF,WAEjB,SAAC0wC,KAAAA,CAAG1wC,UAAU,U,SAAU,sBACxB,UAAC8G,GAAAA,GAAGA,CACFmkE,iBAAkB4Q,GAClBlZ,SAAU+Z,EACVc,YAAaD,EACbE,aAAc,IAAMN,EAAU,M,WAI9B,UAAC9wB,GAAAA,GAAMA,CAAClpD,GAAG,YAAYiE,KAAK,UAAU0I,KAAMuvC,GAAgBy8B,G,WAC1D,SAACxvB,GAAAA,GAAKA,CACJnpD,GAAI44E,GACJ30E,KAAK,OACLmlD,MAAO,CAAE,aAAc,UAAW,aAAc,QAElD,SAACD,GAAAA,GAAKA,CACJnpD,GAAG,kBACHiE,KAAK,SACL8nD,OAAQ,CAAE,aAAc,CAAC,MAAO,QAAS,YAAa,IACtD3C,MAAO,CACL,aAAc,UACd,kBAAmB,UACnB,kBAAmB,SAIzB,SAACquB,GAAkBA,CACjB7C,MAAO3rD,EAAIjpB,GACXvB,MAAOA,EACPi3E,MAAOA,EACP9zE,KAAMA,EACNuqD,SAAUysB,GACVlB,UAAW7B,EACX8B,QAASmC,OAGD,OAAX/8C,IACC,UAACvlB,MAAAA,CAAI3a,UAAU,4E,UACZkgC,EAAO3mB,QAAQ,GAAGvL,QAAQ,IAAK,KAAK,YAGxCqvE,IAEC,SAAC1iE,MAAAA,CAAI3a,UAAU,wE,UACb,UAAC2a,MAAAA,CACCrD,KAAK,SACLtX,UAAU,yF,WAEV,SAACs5E,GAAAA,EAAOA,CAACt5E,UAAU,gDACnB,SAAC2a,MAAAA,CAAI3a,UAAU,wB,SAAwB,kBACvC,UAAC2a,MAAAA,CAAI3a,UAAU,6C,UACZgC,EAAM,QAAM62E,EAAM10E,OAAO,8BAE5B,SAACwW,MAAAA,CAAI3a,UAAU,mD,UACb,SAAC2a,MAAAA,CACC3a,UAAU,uCACVyK,MAAO,CAAE0M,MAAU,IAAOnV,EAASlB,KAAKkX,IAAI,EAAG6gE,EAAM10E,QAArC,UAGpB,SAACwW,MAAAA,CAAI3a,UAAU,gC,SAAgC,4CAIpDq8E,IACC,UAACt8E,EAAAA,EAAMA,CACLN,KAAK,KACLP,QAAQ,YACRc,UAAU,mCACV4d,QAAS,IAAM6+D,GAAY,G,WAE3B,SAACiB,GAAAA,EAAIA,CAAC19E,UAAU,WAAW,oBAI9Bq8E,GAAQG,IAAY,SAACrE,GAAWA,CAACC,MAAOiE,EAAKjE,MAAOxrB,QAAS,IAAM6vB,GAAY,MAChF,SAAC9hE,MAAAA,CAAI3a,UAAU,mC,UACb,SAAC44E,GAAQA,CACPC,MAAOA,EACP9zE,KAAMA,EACN+zE,UAAWA,EACXj5D,aAAcs6D,GAAkB/tD,GAChC4sD,OAAQA,EACR/kC,MAAOopC,EACPtE,gBAAiBA,EACjBE,OAAQ8D,EACR7D,SAAUgE,EACV/D,kBAAmBwD,QAK7B,CCnLA,MA6BA,IA7BegB,EAAAA,EAAAA,IAAoB,CACjC,CAAEnwE,KAAM,IAAKowE,OAAQ,KAAMhsE,EAAAA,EAAAA,IAAS,eACpC,CAAEpE,KAAM,QAASquC,UCJL,KACZ,MAAM,MAAEjmC,EAAK,KAAEf,EAAI,UAAEW,GAAcc,IAC7BunE,GAAWC,EAAAA,EAAAA,OACVtsC,IAAgBC,EAAAA,EAAAA,OAEhB37B,EAAOioE,IAAY//E,EAAAA,EAAAA,UAAS,KAC5B+X,EAAUioE,IAAehgF,EAAAA,EAAAA,UAAS,KAClC8L,EAAOomC,IAAYlyC,EAAAA,EAAAA,UAAwB,OAC3CigF,EAAcC,IAAmBlgF,EAAAA,EAAAA,WAAS,GA+BjD,OA7BAC,EAAAA,EAAAA,WAAU,KACR,IAAIuX,GAGAX,EAAM,CACR,MAAMyX,EAAOklB,EAAa/oC,IAAI,SAAW,aACzCo1E,EAASvxD,EAAM,CAAEte,SAAS,GAC5B,GACC,CAACwH,EAAWqoE,EAAUrsC,EAAc38B,KAsBrC,SAAC8F,MAAAA,CAAI3a,UAAU,2E,UACb,SAAC2a,MAAAA,CAAI3a,UAAU,+B,UACb,UAAC2a,MAAAA,CAAI3a,WAAWQ,EAAAA,EAAAA,IAAG,uB,WACjB,SAAC+5C,MAAAA,CAAIC,IAAKC,GAAMC,IAAI,qCAAqC16C,UAAU,kBACnE,SAAC2mC,GAAIA,CAAC3mC,UAAU,6B,UACd,UAAC8mC,GAAWA,CAAC9mC,UAAU,0B,WACrB,SAACm+E,OAAAA,CAAKn+E,UAAU,aAAao+E,SA1BpBhgF,MAAO+R,IAE1B,GADAA,EAAEm/B,kBACE95B,IAAayoE,EAAjB,CAGA/tC,EAAS,MACTguC,GAAgB,GAEhB,UACQtoE,EAAME,EAAOC,GACnB,MAAMuW,EAAOklB,EAAa/oC,IAAI,SAAW,aACzCo1E,EAASvxD,EAAM,CAAEte,SAAS,GAC5B,CAAE,MAAOpE,GACPsmC,EAAStmC,aAAeV,MAAQU,EAAIuM,QAAU,oBAChD,CAAE,QACA+nE,GAAgB,EAClB,CAZA,G,UAuBY,UAACvjE,MAAAA,CAAI3a,UAAU,sB,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,yC,WACb,SAAC0wC,KAAAA,CAAG1wC,UAAU,qB,SAAqB,kBACnC,SAAC6X,IAAAA,CAAE7X,UAAU,qC,SAAqC,qCAKnD8J,IACC,SAAC6Q,MAAAA,CAAI3a,UAAU,kE,SACZ8J,KAIL,UAAC6Q,MAAAA,CAAI3a,UAAU,a,WACb,SAACm8C,GAAKA,CAACu9B,QAAQ,Q,SAAQ,iBACvB,SAACtlC,GAAKA,CACJjxC,GAAG,QACHiE,KAAK,QACLi3E,aAAa,QACbC,UAAQ,EACR5zE,MAAOoL,EACP0gC,SAAWrmC,GAAM4tE,EAAS5tE,EAAE02C,OAAOn8C,aAIvC,UAACiQ,MAAAA,CAAI3a,UAAU,a,WACb,SAACm8C,GAAKA,CAACu9B,QAAQ,W,SAAW,gBAC1B,SAACtlC,GAAKA,CACJjxC,GAAG,WACHiE,KAAK,WACLi3E,aAAa,mBACbC,UAAQ,EACR5zE,MAAOqL,EACPygC,SAAWrmC,GAAM6tE,EAAY7tE,EAAE02C,OAAOn8C,aAI1C,SAAC3K,EAAAA,EAAMA,CAACqH,KAAK,SAASpH,UAAU,SAAS6iB,SAAUo7D,GAAgBzoE,E,SAChEyoE,EAAe,WAAa,mBAInC,SAACtjE,MAAAA,CAAI3a,UAAU,oC,UACb,SAACu6C,MAAAA,CACCC,IAAK+jC,GACL7jC,IAAI,6BACJ16C,UAAU,qGDzF1B,CACE67C,UEhBoB,KACtB,MAAM,KAAEhnC,EAAI,UAAEW,GAAcc,IACtBuU,GAAWgvB,EAAAA,EAAAA,MAEjB,GAAIrkC,EACF,OAAO,KAGT,IAAKX,EAAM,CACT,MAAMyX,EAAOzB,EAASgE,SAAWhE,EAASpe,OAASoe,EAAS2zD,KAC5D,OAAO,SAACC,EAAAA,GAAQA,CAACx2C,GAAI,eAAev/B,mBAAmB4jB,KAASte,SAAO,GACzE,CAEA,OAAO,SAAC0wE,EAAAA,GAAMA,CAAAA,IFIZppE,SAAU,CACR,CACE9H,KAAM,MACNquC,UGZe,KACrB,UAAC/F,GAAeA,CAACC,YAAanrB,OAAO/R,YALF,K,WAMjC,SAACkhC,GAAUA,CAAAA,IACX,UAAChC,GAAYA,CAAC/3C,UAAU,U,WAItB,SAAC2a,MAAAA,CAAI3a,UAAU,0D,UACb,SAAC63C,GAAcA,CAAC73C,UAAU,UAAUuX,aAAW,mBAEjD,SAACoD,MAAAA,CAAI3a,UAAU,wD,UACb,SAAC0+E,EAAAA,GAAMA,CAAAA,SAGX,SAAC5jC,GAAOA,CAAAA,MHDJxlC,SAAU,CACR,CAAEskB,OAAO,EAAMgkD,OAAQ,KAAMhsE,EAAAA,EAAAA,IAAS,eACtC,CAAEpE,KAAM,QAASquC,UP2DZ,WACb,OACE,SAACyF,GAAaA,C,UACZ,SAAC61B,GAAAA,CAAAA,IAGP,GOhEU,CAAE3pE,KAAM,gBAAiBquC,UI1BpB,WACb,OAAO,SAACmgC,GAAiBA,CAAAA,EAC3B,GJyBU,CAAExuE,KAAM,UAAWquC,UKxBR,KACnB,MAAM,KAAEhnC,GAASyB,IAEjB,OAAKzB,GAAMolC,oBAIJ,SAAC0kC,GAAOA,CAAAA,IAHN,SAACF,EAAAA,GAAQA,CAACx2C,GAAG,aAAaj6B,SAAO,MLqBlC,CAAER,KAAM,cAAequC,U3DmBR,KACvB,MAAM,KAAEhnC,GAASyB,IAEjB,OAAKzB,GAAMolC,oBAKT,SAAC2B,GAAAA,C,UACC,SAACgjC,EAAAA,SAAQA,CACPj0D,UAAU,SAAChQ,MAAAA,CAAI3a,UAAU,oC,SAAoC,uB,UAE7D,SAAC07C,GAAAA,CAAAA,QARE,SAAC+iC,EAAAA,GAAQA,CAACx2C,GAAG,aAAaj6B,SAAO,M2DtBlC,CAAER,KAAM,eAAgBquC,UtDmHd,KAClB,MAAOn0C,EAAQm3E,IAAa7gF,EAAAA,EAAAA,UAAS,iBAC/B8gF,EA/HR,WACE,MAAOvgC,EAASwgC,IAAc/gF,EAAAA,EAAAA,UAAwB,IA4BtD,MAAO,CAAEugD,UAASygC,aA1BG,CAACplD,EAAe1lB,KACnC6qE,EAAYE,GAASA,EAAKr0E,IAAI,CAAC+K,EAAGlS,IAAOA,IAAMm2B,EAAQ,IAAKjkB,KAAMzB,GAAUyB,KAyB9CupE,WAtBb,CAACC,EAAwBC,KAC1C,IAAKD,EAAO,OAEZ,MAAME,EAA4B3yE,MAAM43B,KAAK66C,GAAOv0E,IAAK6zC,IAAU,CACjEA,OACArH,MAAO,OACPsH,SAAU,KAGZqgC,EAAYE,IACV,MAAMK,EAAaL,EAAK96E,OAIxB,OAHAk7E,EAAW/zE,QAAQ,CAAC+iB,EAAO5qB,KACzB27E,EAAY/wD,EAAMowB,KAAM6gC,EAAa77E,KAEhC,IAAIw7E,KAASI,MAQoBE,UAJ1B,KAChBR,EAAYE,GAASA,EAAKh9E,OAAQ0T,GAAkB,SAAZA,EAAEyhC,OAAgC,UAAZzhC,EAAEyhC,SAIpE,CAiGsBooC,IACbC,EAAgBC,IAAqB1hF,EAAAA,EAAAA,WAAS,GAC/C2hF,GAAgBl/C,EAAAA,EAAAA,QAAyB,MAEzCm/C,GAAmB/pE,EAAAA,EAAAA,aACvBzX,MAAOqgD,EAAY7kB,KACjB,IACEklD,EAAYE,aAAaplD,EAAO,CAAEwd,MAAO,YAAasH,SAAU,IAEhE,MAAM,KAAE5uC,SjDmlBuDpE,EiDnlBvB,CACtC5C,KAAM,CAAEpB,SAAQm4E,kBAAmBphC,EAAKj0C,QjDmlBtCkB,GAASoD,QAAUA,GAAQqF,KAAuD,CACtFnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,mBACFsI,EACH7C,QAAS,CACL,eAAgB,sBACb6C,GAAS7C,YiD9lBhB,IAAKiH,EAAM,MAAM,IAAI5G,MAAM,2BAE3B,MAAM42E,EAAWhwE,EAAK3M,GACtB27E,EAAYE,aAAaplD,EAAO,CAAEkmD,WAAUphC,SAAU,WA9G9D,SACED,EACAshC,GAGA,OAAO,IAAI9uE,QAAQ,CAACC,EAASkpD,KAC3B,MAAM4lB,EAAM,IAAIC,eAChBD,EAAIhqC,KAAK,MAAO+pC,GAChBC,EAAIE,iBAAiB,iBAAkB,aACvCF,EAAIE,iBAAiB,eAAgB,4BAErCF,EAAIG,OAAOC,WAAcjwE,IAqGqB,IAACkwE,EApGzClwE,EAAEmwE,mBAoGuCD,EAnGhCv/E,KAAK4c,MAAM,EAAG6iE,OAASpwE,EAAEqwE,MAAS,KAoG3C1B,EAAYE,aAAaplD,EAAO,CAAE8kB,SAAU,GAAK59C,KAAK4c,MAAY,GAAN2iE,OAhGlEL,EAAIS,OAAS,IACXT,EAAI72E,QAAU,KAAO62E,EAAI72E,OAAS,IAC9B+H,IACAkpD,EAAO,IAAIlxD,MAAM,kBAAkB82E,EAAI72E,WAC7C62E,EAAIU,QAAU,IAAMtmB,EAAO,IAAIlxD,MAAM,gCACrC82E,EAAIW,KAAKliC,IAEb,CAwFcmiC,CAAaniC,EAAM3uC,EAAK+wE,YAI9B/B,EAAYE,aAAaplD,EAAO,CAAEwd,MAAO,aAAcsH,SAAU,UjDgqB5B,CAAuChzC,IACxEA,EAAQoD,QAAUA,GAAQqF,KAAoE,CAClGnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,uCACFsI,EACH7C,QAAS,CACL,eAAgB,sBACb6C,EAAQ7C,WiD5qBTi4E,CAA8B,CAClCtzE,KAAM,CAAErK,GAAI28E,GACZh3E,KAAM,CAAC,IAGTg2E,EAAYE,aAAaplD,EAAO,CAAEwd,MAAO,OAAQsH,SAAU,KAC7D,CAAE,MAAO90C,GACP,MAAMuM,EAAUvM,aAAeV,MAAQU,EAAIuM,QAAU,iBACrD2oE,EAAYE,aAAaplD,EAAO,CAAEwd,MAAO,QAASttC,MAAOqM,GAC3D,CjD4jB0B,IAAuCzK,GiD1jBnE,CAAChE,IAGGq5E,GAAoBlrE,EAAAA,EAAAA,aACvBspE,GAA2BL,EAAYI,WAAWC,EAAOS,GAC1D,CAACA,IAGGoB,GAAmBnrE,EAAAA,EAAAA,aACtB1F,IACCA,EAAEm/B,iBACFowC,GAAkB,GAClBqB,EAAkB5wE,EAAE8wE,aAAa9B,QAEnC,CAAC4B,IAGH,OACE,UAACpmE,MAAAA,CAAI3a,UAAU,a,WACb,SAACk0C,GAAUA,CACTj4B,MAAM,UACNw0B,YAAY,+DAEd,UAAC0D,GAAWA,CACVl4B,MAAM,gBACNw0B,YAAY,4I,WAEZ,UAAChK,KAAAA,CAAGzmC,UAAU,yD,WACZ,UAAC0mC,KAAAA,C,UAAG,aACS,KACX,SAAC/pB,OAAAA,CAAK3c,UAAU,uC,SAAuC,6BAAgC,IAAI,SACpF,KACP,SAAC2c,OAAAA,CAAK3c,UAAU,uC,SAAuC,sBAAwB,QAEjF,UAAC0mC,KAAAA,C,UAAG,oBACgB,KAClB,SAAC/pB,OAAAA,CAAK3c,UAAU,uC,SAAuC,6BAAgC,IAAI,SACpF,KACP,SAAC2c,OAAAA,CAAK3c,UAAU,uC,SAAuC,sBAAwB,WAGnF,SAAC6X,IAAAA,CAAE7X,UAAU,qC,SAAqC,6DAKpD,SAACm0C,GAAWA,CAACl4B,MAAM,0BAA0Bw0B,YAAY,8B,UACvD,UAAC91B,MAAAA,CAAI3a,UAAU,wB,WACb,SAACm8C,GAAKA,CAACu9B,QAAQ,S,SAAS,UACxB,SAACtlC,GAAKA,CACJjxC,GAAG,SACHuH,MAAOhD,EACP8uC,SAAWrmC,GAAM0uE,EAAU1uE,EAAE02C,OAAOn8C,OACpCw2E,YAAY,8BAKlB,UAAC/sC,GAAWA,CACVl4B,MAAM,qBACNw0B,YAAY,8D,WAEZ,SAACkO,GAAAA,CACCC,SAAU6gC,EACV5gC,WAAa1uC,IACXA,EAAEm/B,iBACFowC,GAAkB,IAEpB5gC,YAAa,IAAM4gC,GAAkB,GACrC3gC,OAAQiiC,EACRpjE,QAAS,IAAM+hE,EAAc/7C,SAASrhB,QACtCrF,MAAM,wDAER,SAACm3B,QAAAA,CACChf,IAAKsqD,EACLv4E,KAAK,OACL+5E,OAAO,OACPC,UAAQ,EACRphF,UAAU,SACVw2C,SAAWrmC,GAAM4wE,EAAkB5wE,EAAE02C,OAAOs4B,aAIhD,SAAC7gC,GAAAA,CAAYC,QAASugC,EAAYvgC,QAASC,QAASsgC,EAAYS,iBsDzO5D,CAAE/xE,KAAM,gBAAiBquC,UxD2Cd,KACnB,MAAOj7B,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChCqjF,EAAiBC,IAAsBtjF,EAAAA,EAAAA,UAAS,KAChDujF,EAAcC,IAAmBxjF,EAAAA,EAAAA,UAAS,KAC1Cqe,EAASC,IAActe,EAAAA,EAAAA,UAAkB,mBACzCue,EAASC,IAAcxe,EAAAA,EAAAA,UAAkB,QAqBhDC,EAAAA,EAAAA,WAAU,KAnBYG,WACpB+0B,GAAW,GACX,IACE,MAAM,KAAErjB,SAAemT,GAAgB,CACrCjW,MAAO,CACLwlB,MAAO,OACH6uD,GAAmB,CAAEpvC,UAAW,CAACovC,OACjCE,EAAav8E,QAAU,CAAEmE,OAAQo4E,MAIzCtuD,GADgBnjB,GAAM+iB,SAAW,IACb5wB,OAAQob,GAAqB,YAAdA,EAAGlU,QACxC,CAAE,MACA8pB,EAAY,GACd,CAAE,QACAE,GAAW,EACb,GAIAtB,IACC,CAACwvD,EAAiBE,IAErB,MAAME,EAAc75E,IACdA,IAAQyU,EACVG,EAAYvX,GAAa,QAANA,EAAc,OAAS,QAE1CqX,EAAW1U,GACX4U,EAAW,SAITklE,GAAiB75E,EAAAA,EAAAA,SAAQ,KAC7B,MAAM6U,EAAkB,QAAZH,EAAoB,GAAK,EACrC,MAAO,IAAIqE,GAAU5d,KAAK,CAACP,EAAGC,KAC5B,IAAIoa,EACAC,EACJ,OAAQV,GACN,IAAK,iBAGH,OAFAS,EAAKra,EAAEwe,gBAAkB,GACzBlE,EAAKra,EAAEue,gBAAkB,GAClBvE,EAAM,EAAe/Z,cAAcoa,GAC5C,IAAK,cAGH,OAFAD,EAAKra,EAAEoa,aAAe,GACtBE,EAAKra,EAAEma,aAAe,GACfH,EAAM,EAAe/Z,cAAcoa,GAC5C,IAAK,SAGH,OAFAD,EAAKra,EAAE0G,QAAU,GACjB4T,EAAKra,EAAEyG,QAAU,GACVuT,EAAM,EAAe/Z,cAAcoa,GAC5C,IAAK,YAGH,OAFAD,EAAKra,EAAE+sE,YAAa,IACpBzyD,EAAKra,EAAE8sE,YAAa,IACb9yD,GAAQI,EAAiBC,GAClC,QACE,OAAO,MAGZ,CAAC6D,EAAUvE,EAASE,IAOvB,OACE,UAAC5B,MAAAA,CAAI3a,UAAU,a,WACb,SAACk0C,GAAUA,CACTj4B,MAAM,WACNw0B,YAAY,iEAGd,SAAC0D,GAAWA,CAACl4B,MAAM,U,UACjB,UAACtB,MAAAA,CAAI3a,UAAU,4B,WACb,UAAC2a,MAAAA,CAAI3a,UAAU,e,WACb,SAACm8C,GAAKA,CAACu9B,QAAQ,a,SAAa,eAC5B,SAACtlC,GAAKA,CACJjxC,GAAG,aACH+9E,YAAY,WACZx2E,MAAO22E,EACP7qC,SAAWrmC,GAAMmxE,EAAmBnxE,EAAE02C,OAAOn8C,aAGjD,UAACiQ,MAAAA,CAAI3a,UAAU,e,WACb,SAACm8C,GAAKA,C,SAAC,YACP,UAACE,GAAMA,CAAC3xC,MAAO62E,EAAc7/D,cAAe8/D,E,WAC1C,SAAChlC,GAAaA,C,UACZ,SAACD,GAAWA,CAAC2kC,YAAY,YAE3B,UAACxkC,GAAaA,C,WACZ,SAACG,GAAUA,CAACnyC,MAAM,I,SAAI,UACtB,SAACmyC,GAAUA,CAACnyC,MAAM,Q,SAAQ,YAC1B,SAACmyC,GAAUA,CAACnyC,MAAM,Y,SAAY,kBAC9B,SAACmyC,GAAUA,CAACnyC,MAAM,a,SAAa,eAC/B,SAACmyC,GAAUA,CAACnyC,MAAM,W,SAAW,4BAOvC,SAACypC,GAAWA,CAACl4B,MAAM,W,SAChBiX,GACC,SAACrb,IAAAA,CAAE7X,UAAU,gC,SAAgC,WACvB,IAApB4gB,EAASzc,QACX,SAAC0T,IAAAA,CAAE7X,UAAU,gC,SAAgC,6BAE7C,UAAC0a,EAAKA,C,WACJ,SAACG,EAAWA,C,UACV,UAACI,EAAQA,C,WACP,SAACE,EAASA,CAACnb,UAAU,O,SAAO,OAC5B,SAACw9C,GAAAA,CACCtgC,MAAM,YACNb,QAAQ,iBACRohC,UAAWphC,EACXqhC,UAAWnhC,EACXohC,OAAQ8jC,KAEV,SAACjkC,GAAAA,CACCtgC,MAAM,eACNb,QAAQ,cACRohC,UAAWphC,EACXqhC,UAAWnhC,EACXohC,OAAQ8jC,KAEV,SAACjkC,GAAAA,CACCtgC,MAAM,SACNb,QAAQ,SACRohC,UAAWphC,EACXqhC,UAAWnhC,EACXohC,OAAQ8jC,KAEV,SAACjkC,GAAAA,CACCtgC,MAAM,mBACNb,QAAQ,YACRohC,UAAWphC,EACXqhC,UAAWnhC,EACXohC,OAAQ8jC,KAEV,SAACtmE,EAASA,CAACnb,UAAU,eAGzB,SAAC+a,EAASA,C,SACP2mE,EAAe92E,IAAI,CAACyS,EAAIuc,KACvB,MAAM+nD,EAAKtkE,EAAGlU,OAAS6zC,GAAc3/B,EAAGlU,QAAU6zC,GAAcC,SAChE,OACE,UAAChiC,EAAQA,C,WACP,SAACI,EAASA,CAACrb,UAAU,wB,SAAyB45B,EAAQ,KACtD,SAACve,EAASA,C,UACR,SAAC2sB,EAAAA,GAAIA,CACHC,GAAI,sBAAsB5qB,EAAGla,KAC7BnD,UAAU,8B,SAETqd,EAAG4D,oBAGR,SAAC5F,EAASA,CAACrb,UAAU,qC,SAClBqd,EAAGR,YACA,IAAI1R,KAAKkS,EAAGR,YAAc,aAAaf,mBAAmB,SAC1D,OAEN,SAACT,EAASA,C,UACR,SAACZ,EAAKA,CAACvb,QAASyiF,EAAGziF,Q,SAAUyiF,EAAGzkE,WAElC,SAAC7B,EAASA,CAACrb,UAAU,e,SACF,MAAhBqd,EAAGmyD,WAAqC,MAAhBnyD,EAAG4xC,UACxB,GAAG5xC,EAAGmyD,UAAUj2D,QAAQ,QAAQ8D,EAAG4xC,UAAU11C,QAAQ,OACrD,OAEN,SAAC8B,EAASA,C,UACR,SAACtb,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,sDACV4d,QAAS,IAlHVxf,OAAO+E,UACpB+f,GAA4B,CAAE1V,KAAM,CAAErK,QAC5C8vB,EAAagsD,GAASA,EAAKh9E,OAAQob,GAAOA,EAAGla,KAAOA,KAgHjBy+E,CAAavkE,EAAGla,I,UAE/B,SAAC0+E,GAAAA,EAAMA,CAAC7hF,UAAU,mBA9BTqd,EAAGla,kBwDpM1B,CAAEqK,KAAM,oBAAqBquC,UMbb,KACxB,MAAM,GAAE14C,IAAO2+E,EAAAA,EAAAA,KACTjE,GAAWC,EAAAA,EAAAA,OACVlhE,EAASmlE,IAAc/jF,EAAAA,EAAAA,UAA+B,OACtDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChCgkF,EAAeC,IAAoBjkF,EAAAA,EAAAA,WAAS,IAC5CkkF,EAAaC,IAAkBnkF,EAAAA,EAAAA,WAAS,IACxCokF,EAAWC,IAAgBrkF,EAAAA,EAAAA,UAAS,IAErCskF,EAAelkF,UACnB+0B,GAAW,GACX,IACE,MAAM,KAAErjB,S7G4P4DpE,E6G5PzB,CAAE8B,KAAM,CAAErK,GAAIuB,OAAOvB,M7G6P1DuI,EAAQoD,QAAUA,GAAQrG,IAAyD,CACvFuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,yBACFsI,K6GtQLq2E,EAAWjyE,GAAQ,KACrB,CAAE,MACAiyE,EAAW,KACb,CAAE,QACA5uD,GAAW,EACb,C7GsP+B,IAAuCznB,I6GnPxEzN,EAAAA,EAAAA,WAAU,KACRqkF,KACC,CAACn/E,IAEJ,MA6BMo/E,EAAiBnkF,UACrB,GAAKwe,EAAL,CACAqlE,GAAiB,GACjB,U7GmtB0Ev2E,E6GltB1C,CAC5B8B,KAAM,CAAErK,GAAIyZ,EAAQ4lE,QACpB15E,KAAM,CAAE+T,YAAaulE,GAAa,Q7GitB9B12E,EAAQoD,QAAUA,GAAQoF,MAA+D,CAC7FlE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,wBACFsI,EACH7C,QAAS,CACL,eAAgB,sBACb6C,EAAQ7C,Y6G5tBjBs5E,GAAe,GACfG,GACF,CAAE,QACAL,GAAiB,EACnB,C7G0sBmC,IAAuCv2E,C6GrtBtD,GActB,GAAIwnB,EACF,OACE,UAACvY,MAAAA,CAAI3a,UAAU,a,WACb,SAACq1C,GAAQA,CAACr1C,UAAU,qBACpB,SAACq1C,GAAQA,CAACr1C,UAAU,uBAK1B,IAAK4c,EACH,OACE,SAACs3B,GAAUA,CAACj4B,MAAM,wBAAwBw0B,YAAY,uCAI1D,MAAMgyC,EAAsB7lE,EAAQC,YAChC,IAAI1R,KAAKyR,EAAQC,YAAc,aAAaf,mBAAmB,SAC/D,KAEE4mE,EAAsC,CAC1C,CAAC,YAAa9lE,EAAQqE,gBACtB,CAAC,SAAUrE,EAAQzT,QAAU,MAC7B,CAAC,aAAc,IAAIgC,KAAKyR,EAAQ+lE,SAAS3hF,eAAe,UACxD,CAAC,mBAAoB,IAAImK,KAAKyR,EAAQgmE,UAAU5hF,eAAe,WAG3D6hF,EAAwC,CAC5C,CACE,oBACqB,MAArBjmE,EAAQs2C,UACJ,GAAGt2C,EAAQs2C,UAAU35C,QAAQ,OAAOqD,EAAQu2C,WAAW55C,QAAQ,KAC/D,MAEN,CACE,oBACqB,MAArBqD,EAAQw2C,UACJ,GAAGx2C,EAAQw2C,UAAU75C,QAAQ,OAAOqD,EAAQy2C,WAAW95C,QAAQ,KAC/D,MAEN,CACE,mBACwB,MAAxBqD,EAAQkmE,aAAuB,GAAGlmE,EAAQkmE,kBAAkBlmE,EAAQmmE,iBAAmB,MAEzF,CAAC,aAAmC,MAArBnmE,EAAQ4yD,UAAoB,GAAG5yD,EAAQ4yD,UAAUj2D,QAAQ,OAAS,MACjF,CAAC,aAAmC,MAArBqD,EAAQqyC,UAAoB,GAAGryC,EAAQqyC,UAAU11C,QAAQ,OAAS,MACjF,CAAC,mBAA0C,MAAtBqD,EAAQwyD,WAAqB,GAAGxyD,EAAQwyD,WAAW71D,QAAQ,OAAS,OAG3F,OACE,UAACoB,MAAAA,CAAI3a,UAAU,a,WACb,SAACk0C,GAAUA,CACTj4B,MAAO,WAAWW,EAAQqE,iBAC1BwvB,YAAY,iD,UAEZ,SAACh2B,EAAKA,CACJvb,QACqB,UAAnB0d,EAAQzT,QAAyC,cAAnByT,EAAQzT,OAAyB,UAAY,Y,SAG5EyT,EAAQzT,YAIb,SAACgrC,GAAWA,CAACl4B,MAAM,gB,UACjB,UAACqzD,KAAAA,CAAGtvE,UAAU,oC,WACZ,UAAC2a,MAAAA,CAAI3a,UAAU,sE,WACb,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAwB,eACtC,SAAC2vE,KAAAA,CAAG3vE,UAAU,sC,SACXkiF,GACC,sB,WACE,SAAC9tC,GAAKA,CACJhtC,KAAK,OACLsD,MAAO03E,EACP5rC,SAAWrmC,GAAMkyE,EAAalyE,EAAE02C,OAAOn8C,OACvC1K,UAAU,aACVgqD,UAAY75C,IACI,UAAVA,EAAEvI,KAAiB26E,IACT,WAAVpyE,EAAEvI,KAAkBu6E,GAAe,OAG3C,SAACpiF,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACV4d,QAAS2kE,EACT1/D,SAAUm/D,E,UAEV,SAACgB,GAAAA,EAAKA,CAAChjF,UAAU,gBAEnB,SAACD,EAAAA,EAAMA,CACLb,QAAQ,QACRO,KAAK,OACLO,UAAU,SACV4d,QAAS,IAAMukE,GAAe,G,UAE9B,SAACh4B,GAAAA,EAACA,CAACnqD,UAAU,mBAIjB,sB,UACGyiF,GAAuB,KACxB,SAAC1iF,EAAAA,EAAMA,CAACb,QAAQ,QAAQO,KAAK,OAAOO,UAAU,SAAS4d,QAzHhD,KACrBykE,EAAazlE,GAASC,aAAe,IACrCslE,GAAe,I,UAwHC,SAAClT,GAAAA,EAAMA,CAACjvE,UAAU,qBAM3B0iF,EAAS93E,IAAK,I,IAACsS,EAAOxS,GAAM,E,OAC3B,UAACiQ,MAAAA,CAAgB3a,UAAU,yD,WACzB,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAyBkd,KACvC,SAACyyD,KAAAA,CAAG3vE,UAAU,c,SAAe0K,GAAS,QAF9BwS,WAQhB,SAACi3B,GAAWA,CAACl4B,MAAM,iB,SACK,MAArBW,EAAQs2C,WACP,SAACr7C,IAAAA,CAAE7X,UAAU,gC,SAAgC,oDAI7C,SAACsvE,KAAAA,CAAGtvE,UAAU,oC,SACX6iF,EAAWj4E,IAAK,I,IAACsS,EAAOxS,GAAM,E,OAC7B,UAACiQ,MAAAA,CAAgB3a,UAAU,yD,WACzB,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAyBkd,KACvC,SAACyyD,KAAAA,CAAG3vE,UAAU,2B,SAA4B0K,GAAS,QAF3CwS,UASlB,SAACi3B,GAAWA,CAACl4B,MAAM,c,UACjB,UAACqzD,KAAAA,CAAGtvE,UAAU,qB,WACZ,UAAC2a,MAAAA,CAAI3a,UAAU,8B,WACb,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAwB,eACtC,SAAC2vE,KAAAA,CAAG3vE,UAAU,oC,SAAqC4c,EAAQqmE,eAAiB,UAE9E,UAACtoE,MAAAA,CAAI3a,UAAU,8B,WACb,SAAC0vE,KAAAA,CAAG1vE,UAAU,wB,SAAwB,SACtC,SAAC2vE,KAAAA,CAAG3vE,UAAU,oC,SAAqC4c,EAAQsmE,eAAiB,eAKlF,UAAC/uC,GAAWA,CAACl4B,MAAM,e,WACjB,SAACq4B,GAASA,CAACt0C,UAAU,UACrB,UAAC2a,MAAAA,CAAI3a,UAAU,uB,WACb,SAACD,EAAAA,EAAMA,CAACb,QAAQ,UAAUO,KAAK,KAAKojB,SAAUm/D,EAAepkE,QAlM7Cxf,UACtB,GAAKwe,EAAL,CACAqlE,GAAiB,GACjB,U7G8T6Ev2E,E6G7T1C,CAC/B8B,KAAM,CAAErK,GAAIyZ,EAAQzZ,M7G6ThBuI,EAAQoD,QAAUA,GAAQqF,KAAiE,CAC/FnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,mCACFsI,K6GrUL42E,GACF,CAAE,QACAL,GAAiB,EACnB,C7GuTsC,IAAuCv2E,C6GhUzD,G,SAiMyE,uBAGvF,SAAC3L,EAAAA,EAAMA,CAACb,QAAQ,cAAcO,KAAK,KAAKojB,SAAUm/D,EAAepkE,QAxLpDxf,UACnB,GAAKwe,EAAL,CACAqlE,GAAiB,GACjB,UACQ/+D,GAA4B,CAAE1V,KAAM,CAAErK,GAAIyZ,EAAQzZ,MACxD06E,EAAS,qBACX,CAAE,QACAoE,GAAiB,EACnB,CAPoB,G,SAuL0E,2BNhNxF,CAAEz0E,KAAM,YAAaquC,UvDDd,KACf,MAAO4pB,EAAM0d,IAAWnlF,EAAAA,EAAAA,UAA0B,KAC3Ck1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,GAEjColF,EAAYhlF,iBAAqB,UAAdilF,OAAAA,QAAAA,IAAAA,UAAAA,IAAAA,UAAAA,IACNlwD,GAAW,GAC5B,IACE,MAAM,KAAErjB,ShD6aoDpE,EgD7azB,CAAEsB,MAAO,CAAEwlB,MAAO,OhD8a/C9mB,GAASoD,QAAUA,GAAQrG,IAAiD,CAChFuH,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,gBACFsI,KgDvbLy3E,EAAQrzE,GAAM+iB,SAAW,GAC3B,CAAE,MACAswD,EAAQ,GACV,CAAE,QACAhwD,GAAW,EACb,ChDuauB,IAAuCznB,CgDtahE,GAEAzN,EAAAA,EAAAA,WAAU,KACRmlF,GAAU,GAEV,MAAM7wB,EAAW1zD,YAAYukF,EAAW,KACxC,MAAO,IAAMtkF,cAAcyzD,IAC1B,IAEH,MAAM+wB,EAAa7d,EAAKpiE,KAAMM,GAAmB,YAAbA,EAAEwF,QAAqC,YAAbxF,EAAEwF,QAEhE,OACE,UAACwR,MAAAA,CAAI3a,UAAU,a,WACb,UAACk0C,GAAUA,CACTj4B,MAAM,aACNw0B,YAAY,kE,UAEX6yC,IACC,SAACvnE,OAAAA,CAAK/b,UAAU,gC,SAAgC,8BAElD,SAACD,EAAAA,EAAMA,CAACb,QAAQ,UAAUO,KAAK,KAAKme,QAAS,IAAMwlE,I,SAAa,mBAKlE,SAACjvC,GAAWA,CAACl4B,MAAM,uB,SAChBiX,GACC,SAACrb,IAAAA,CAAE7X,UAAU,gC,SAAgC,WAC3B,IAAhBylE,EAAKthE,QACP,SAAC0T,IAAAA,CAAE7X,UAAU,gC,SAAgC,iCAE7C,UAAC0a,EAAKA,C,WACJ,SAACG,EAAWA,C,UACV,UAACI,EAAQA,C,WACP,SAACE,EAASA,C,SAAC,QACX,SAACA,EAASA,C,SAAC,UACX,SAACA,EAASA,C,SAAC,YACX,SAACA,EAASA,C,SAAC,eACX,SAACA,EAASA,C,SAAC,aACX,SAACA,EAASA,C,SAAC,aACX,SAACA,EAASA,C,SAAC,sBAGf,SAACJ,EAASA,C,SACP0qD,EAAK76D,IAAKw8D,IACT,MAAMua,EAAK/jC,GAAkBwpB,EAAIj+D,QACjC,OACE,UAAC8R,EAAQA,CAAcjb,UAAU,Y,WAC/B,SAACqb,EAASA,CAACrb,UAAU,oB,SAAqBonE,EAAIjkE,MAC9C,SAACkY,EAASA,C,SAAE0iC,GAAgBqpB,EAAImc,WAAanc,EAAImc,YACjD,SAACloE,EAASA,C,UACR,SAACZ,EAAKA,CAACvb,QAASyiF,EAAGziF,Q,SAAUyiF,EAAGzkE,WAElC,SAAC7B,EAASA,C,UACR,UAACV,MAAAA,CAAI3a,UAAU,0B,WACb,SAAC2a,MAAAA,CAAI3a,UAAU,qD,UACb,SAAC2a,MAAAA,CACC3a,UAAU,gDACVyK,MAAO,CAAE0M,MAAO,GAAGiwD,EAAIoc,0BAG3B,UAACznE,OAAAA,CAAK/b,UAAU,6C,UACbonE,EAAIoc,iBAAiB,aAI5B,SAACnoE,EAASA,CAACrb,UAAU,e,SAAgBonE,EAAIob,UACzC,SAACnnE,EAASA,CAACrb,UAAU,wB,SAClB,IAAImL,KAAKi8D,EAAIub,SAAS3hF,eAAe,YAExC,SAACqa,EAASA,CAACrb,UAAU,oF,SAClBonE,EAAIqc,eAAiB,QAxBXrc,EAAIjkE,kBuD3D3B,CAAEqK,KAAM,aAAcquC,UOhBd,KAChB,MAAOj7B,EAAUqS,IAAej1B,EAAAA,EAAAA,UAA0B,KACnDk1B,EAASC,IAAcn1B,EAAAA,EAAAA,WAAS,IAChC0lF,EAASC,IAAc3lF,EAAAA,EAAAA,WAAS,GAEjC4lF,EAAexlF,UACnB+0B,GAAW,GACX,IACE,MAAM,KAAErjB,SAAemT,GAAgB,CAAEjW,MAAO,CAAE7D,OAAQ,UAAWqpB,MAAO,OAC5ES,EAAYnjB,GAAM+iB,SAAW,GAC/B,CAAE,MACAI,EAAY,GACd,CAAE,QACAE,GAAW,EACb,GAsBF,OAJAl1B,EAAAA,EAAAA,WAAU,KACR2lF,KACC,KAGD,UAACjpE,MAAAA,CAAI3a,UAAU,a,WACb,SAACk0C,GAAUA,CACTj4B,MAAM,aACNw0B,YAAY,uHAGd,SAAC0D,GAAWA,CAACl4B,MAAM,uB,SAChBiX,GACC,SAACrb,IAAAA,CAAE7X,UAAU,gC,SAAgC,WACvB,IAApB4gB,EAASzc,QACX,SAAC0T,IAAAA,CAAE7X,UAAU,gC,SAAgC,yBAE7C,sB,WACE,UAAC2a,MAAAA,CAAI3a,UAAU,yC,WACb,UAAC6X,IAAAA,CAAE7X,UAAU,gC,UACV4gB,EAASzc,OAAO,gCAEnB,UAACpE,EAAAA,EAAMA,CACLb,QAAQ,cACRO,KAAK,KACLojB,SAAU6gE,EACV9lE,QAzCMxf,UAClB,GAAKwsB,OAAOi5D,QAAQ,oHAApB,CAGAF,GAAW,GACX,U9GwY2B70E,EAAQqF,KAAkE,CACjGnE,aAAc,OACdV,SAAU,CACN,CACIC,GAAI,SACJ/E,KAAM,YACNpD,KAAM,WAGdhE,IAAK,gCACFsI,U8GhZCk4E,GACR,CAAE,MAEF,CAAE,QACAD,GAAW,EACb,C9GgYuC,IAAuCj4E,C8GzY9E,G,WAwCY,SAACm2E,GAAAA,EAAMA,CAAC7hF,UAAU,iBACjB0jF,EAAU,yBAA2B,0BAG1C,UAAChpE,EAAKA,C,WACJ,SAACG,EAAWA,C,UACV,UAACI,EAAQA,C,WACP,SAACE,EAASA,C,SAAC,eACX,SAACA,EAASA,C,SAAC,YACX,SAACA,EAASA,C,SAAC,wBAGf,SAACJ,EAASA,C,SACP6F,EAAShW,IAAKyS,IACb,UAACpC,EAAQA,C,WACP,SAACI,EAASA,CAACrb,UAAU,c,SAAeqd,EAAG4D,kBACvC,SAAC5F,EAASA,C,UACR,SAACZ,EAAKA,CAACvb,QAAQ,c,SAAc,kBAE/B,SAACmc,EAASA,CAACrb,UAAU,wB,SAClB,IAAImL,KAAKkS,EAAGulE,UAAU5hF,eAAe,aAN3Bqc,EAAGla,oBPzD1B,CAAEqK,KAAM,8BAA+BquC,UAAWioC,IAClD,CAAEt2E,KAAM,8BAA+BquC,UAAWioC,IAClD,CAAEt2E,KAAM,mBAAoBquC,UAAWioC,UQ9BjDh1E,EAAOC,SAAS+F,aAAapF,QAAQqF,IAAKrF,IACxC,MAAM,UAAEsF,IAAcC,EAAAA,EAAAA,IAAYjN,SAASkN,QAI3C,OAHIxF,EAAQ7G,SAAWmM,IACrBtF,EAAQ7G,QAAQ,eAAiBmM,GAE5BtF,IAGT,MAQA,GARY,KACV,SAACq0E,EAAAA,GAAoB,CAACp5D,UAAU,SAAC9S,IAAAA,C,SAAE,0B,UACjC,SAACxC,EAAYA,C,UACX,SAAC2uE,EAAAA,EAAcA,CAACC,OAAQA,SCN9BF,EAAAA,GAAY,CACVG,IAAKt5D,OAAOu5D,WACZC,QAASx5D,OAAOy5D,cAGLC,EAAAA,EAAAA,YAAWt8E,SAASC,eAAe,cAC3Co1B,QAAO,SAACknD,GAAGA,CAAAA,G,2FCdhB,SAAShL,EAAO,G,IAAA,UACdv5E,EAAS,KACTP,EAAO,aACJS,GAHW,EAOd,OACE,SAACskF,EAAAA,GAAoB,CACnBnkF,YAAU,SACVE,YAAWd,EACXO,WAAWQ,EAAAA,EAAAA,IACT,ycACAR,MAEEE,E,UAEJ,SAACskF,EAAAA,GAAqB,CACpBnkF,YAAU,eACVL,WAAWQ,EAAAA,EAAAA,IACT,mVAKV,C,+CCrBA,MAAMikF,EAAU,IAAI39E,IAEpB1I,eAAeoxC,EAAMpsC,EAAakgB,EAAenf,EAAgBoN,GAC/D,MAAM1B,QAAiBvR,MAAM8E,EAAK,CAChCyF,QAAS,CAAE67E,MAAO,SAASphE,KAASA,EAAQnf,EAAS,KACrDoN,WAEF,GAAwB,MAApB1B,EAAS1G,OAAgB,MAAM,IAAID,MAAM,8BAA8B2G,EAAS1G,WACpF,MAAMytB,QAAc/mB,EAASgnB,cAC7B,GAAID,EAAMyC,aAAel1B,EAAQ,MAAM,IAAI+E,MAAM,iCACjD,OAAO0tB,CACT,CAwCOx4B,eAAeo+D,EACpB9sD,EACA4sD,GAEA,MAAOl5D,EAAKs5B,GAAQhtB,EAAQtM,IAAIf,MAAM,IAAsBoC,MAAM,KAC5DyO,EAAS,IAAI8O,IAAI5e,GACvB,IAAK,CAAC,SAAU,SAAS6J,SAASiG,EAAOyxE,UAAW,MAAM,IAAIz7E,MAAM,4BACpE,MAAM07E,QA7CRxmF,eAAqBgF,GACnB,IAAI21B,EAAU0rD,EAAQh8E,IAAIrF,GAC1B,GAAI21B,EAGF,OAFA0rD,EAAQzwE,OAAO5Q,GACfqhF,EAAQh7E,IAAIrG,EAAK21B,GACVA,EA0BT,IAxBAA,EAAU,WACR,MAAMrqB,EAAS,IAAIm2E,eAAer1C,EAAMpsC,EAAK,EAAG,EAAG0hF,YAAYhb,QAAQ,OACvE,GAA4B,aAAxBp7D,EAAOq2E,UAAU,GAAmB,MAAM,IAAI77E,MAAM,+BACxD,MAAM/E,EAASuK,EAAOq2E,UAAU,GAAG,GACnC,GAAI5gF,EAAS,GAAKA,EAAS,SAAkB,MAAM,IAAI+E,MAAM,8BAC7D,MAAMwB,EAAQvC,KAAKC,OACjB,IAAI48E,aAAc9sD,aAAasX,EAAMpsC,EAAK,EAAGe,EAAQ2gF,YAAYhb,QAAQ,QAE3E,IACGp9D,MAAMC,QAAQjC,EAAMsoD,SACG,IAAxBtoD,EAAMsoD,OAAO7uD,SACZuG,EAAMsoD,OAAO/C,MAAMvrD,OAAOi4B,YAC1Bj4B,OAAO6sE,UAAU7mE,EAAM0xD,WACvB13D,OAAO6sE,UAAU7mE,EAAMizD,UACxBjzD,EAAM0xD,QAAU,GAChB1xD,EAAMizD,QAAU,IAChBjzD,EAAM0xD,QAAU1xD,EAAMizD,UACrBjzD,EAAMrD,OACgB,iBAAhBqD,EAAMrD,MAEb,MAAM,IAAI6B,MAAM,iCAClB,MAAO,IAAKwB,EAAOu6E,UAAW,EAAI9gF,EACnC,EAtBS,GAuBVsgF,EAAQh7E,IAAIrG,EAAK21B,GACV0rD,EAAQhlF,KAAO,IAAIglF,EAAQzwE,OAAOywE,EAAQlrD,OAAOjN,OAAO5hB,OAI/D,OAHAquB,EAAQtU,MAAM,KACRggE,EAAQh8E,IAAIrF,KAAS21B,GAAS0rD,EAAQzwE,OAAO5Q,KAE5C21B,CACT,CASyBa,CAAMx2B,GAE7B,GADAk5D,EAAW/qD,OAAO2zE,iBACG,SAAjBx1E,EAAQtI,KACV,MAAO,CACL0I,KAAM,CACJq1E,SAAU,QACV/oB,QAASwoB,EAASxoB,QAClBuB,QAASinB,EAASjnB,QAClB3K,OAAQ4xB,EAAS5xB,OACjB3rD,MAAO,CAAC,cAAcjE,mBAG5B,GAAqB,UAAjBsM,EAAQtI,OAAqB,kBAAkBjG,KAAKu7B,GAAQ,IAC9D,MAAM,IAAIxzB,MAAM,wBAClB,MAAMmlB,EAAQu2D,EAASv9E,MAAMq1B,GAC7B,IAAKrO,EAAO,CAGV,MAAOyG,EAAGxc,EAAGC,GAAKmkB,EAAKj4B,MAAM,KAAKmG,IAAIlG,QAChC0vB,EAAI,GAAKU,EACT5uB,EAAO,EAAKkuB,EAAK,IAAM,IAC3BnuB,GAASqS,EAAI,GAAK8b,EAAK,IAAM,IACzBvL,EAAO/O,GAC4C,IAAvD,KAAM+4C,KAAK/xD,KAAKg2D,KAAKh2D,KAAKqX,IAAM,EAAI,EAAK2B,EAAOsa,KAActzB,KAAKqX,IAC9D23C,EAAGzmD,EAAG8G,EAAGpK,GAAS6+E,EAAS5xB,OAClC,GAAI/sD,EAAO6pD,GAAK5pD,EAAOiK,GAAK0Y,EAAItQ,GAAKlP,GAAKwf,EAAItQ,EAAI,GAAKxS,EACrD,MAAO,CAAE+J,WAAYkmB,kBAAkB,IAAIsI,UAAU,IAAK,OAC5D,MAAM,IAAIp1B,MAAM,wCAClB,CACA,MAAOihB,EAAQhmB,GAAUkqB,EACzB,IACG3pB,OAAO0gF,cAAcj7D,IACtBA,EAAS,IACRzlB,OAAO0gF,cAAcjhF,IACtBA,EAAS,GACTA,EAAS,QAET,MAAM,IAAI+E,MAAM,+BAClB,MAAM0tB,QAAc4Y,EAAMpsC,EAAKwhF,EAASK,UAAY96D,EAAQhmB,EAAQm4D,EAAW/qD,QACzEwkB,QAAeC,kBAAkB,IAAIjU,KAAK,CAAC6U,GAAQ,CAAExvB,KAAM,eAKjE,OAJIk1D,EAAW/qD,OAAOE,UACpBskB,EAAOI,QACPmmC,EAAW/qD,OAAO2zE,kBAEb,CAAEp1E,KAAMimB,EACjB,C,4GC7GO,SAASv1B,IAAG,sDAAG6kF,EAAH,gBACjB,OAAOC,EAAAA,EAAAA,KAAQC,EAAAA,EAAAA,GAAKF,GACtB,C,yFCGA,MAAMG,GAAiBxmF,E,QAAAA,GACrB,gjBACA,CACEC,SAAU,CACRC,QAAS,CACPC,QAAS,iBACTE,QACE,6FAEJI,KAAM,CACJN,QAAS,mBACTQ,GAAI,qBACJC,GAAI,yBAGRE,gBAAiB,CACfZ,QAAS,UACTO,KAAM,a,cChBZ,MAAMgmF,EAAqBz7C,EAAAA,cAIzB,CACAvqC,KAAM,UACNP,QAAS,UACTwmF,QAAS,IAGX,SAASjkE,EAAY,G,IAAA,UACnBzhB,EAAS,QACTd,EAAO,KACPO,EAAI,QACJimF,EAAU,EAAC,SACXpwE,KACGpV,GANgB,EAWnB,OACE,SAACylF,EAAAA,GAAyB,CACxBtlF,YAAU,eACVC,eAAcpB,EACdqB,YAAWd,EACXmmF,eAAcF,EACdj7E,MAAO,CAAE,QAASi7E,GAClB1lF,WAAWQ,EAAAA,EAAAA,IACT,4IACAR,MAEEE,E,UAEJ,SAACulF,EAAmBpvE,SAAQ,CAAC3L,MAAO,CAAExL,UAASO,OAAMimF,W,SAClDpwE,KAIT,CAEA,SAASqM,EAAgB,G,IAAA,UACvB3hB,EAAS,SACTsV,EAAQ,QACRpW,EAAO,KACPO,KACGS,GALoB,EAOvB,MAAMy1B,EAAUqU,EAAAA,WAAiBy7C,GAEjC,OACE,SAACE,EAAAA,GAAyB,CACxBtlF,YAAU,oBACVC,eAAcq1B,EAAQz2B,SAAWA,EACjCqB,YAAWo1B,EAAQl2B,MAAQA,EAC3BmmF,eAAcjwD,EAAQ+vD,QACtB1lF,WAAWQ,EAAAA,EAAAA,IACTglF,EAAe,CACbtmF,QAASy2B,EAAQz2B,SAAWA,EAC5BO,KAAMk2B,EAAQl2B,MAAQA,IAExB,6DACA,8OACAO,MAEEE,E,SAEHoV,GAGP,C,iICtDO,MAAMuwE,EAAwC,CAAExlD,KAAM,WAAYzsB,KAAM,GAAIoE,KAAM,IAGnF2hE,EAA4C,CAChD,CAAC,EAAK,EAAG,GAAI,KACb,CAAC,IAAM,GAAI,GAAI,KACf,CAAC,GAAK,GAAI,IAAK,KACf,CAAC,IAAM,GAAI,IAAK,KAChB,CAAC,GAAK,GAAI,IAAK,KACf,CAAC,IAAM,IAAK,IAAK,IACjB,CAAC,GAAK,IAAK,IAAK,IAChB,CAAC,EAAK,IAAK,IAAK,KA0BZmM,EAAiD,CACrD,CAAC,EAAG,IAAK,IAAK,IACd,CAAC,EAAG,IAAK,IAAK,IACd,CAAC,EAAG,IAAK,IAAK,IACd,CAAC,EAAG,IAAK,IAAK,IACd,CAAC,GAAI,IAAK,GAAI,KAEHC,EAAgB,GAoBtB,SAAS3lD,EACdpC,G,IACA1vB,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA8Bu3E,EAE9B,MAAMG,EAAUhoD,GAAS1vB,EAAOzM,OAAS,GACzC,OAAImkF,GAAW13E,EAAO0J,IAvDxB,SAA0ByU,GACxB,MAAM8gD,EAAUzsE,KAAKkX,IAAI,EAAGlX,KAAK8S,IAAI,EAAG6Y,IACxC,IAAK,IAAIhpB,EAAI,EAAGA,EAAIk2E,EAAMx1E,OAAS,EAAGV,IAAK,CACzC,MAAOq2E,EAAImM,EAAIC,EAAIC,GAAMxM,EAAMl2E,IACxBu2E,EAAIoM,EAAIC,EAAIC,GAAM3M,EAAMl2E,EAAI,GACnC,GAAI8pE,GAAWuM,GAAMvM,GAAWyM,EAAI,CAClC,MAAM3wE,GAAKkkE,EAAUuM,IAAOE,EAAKF,GACjC,MAAO,CACLh5E,KAAK4c,MAAMuoE,EAAK58E,GAAK+8E,EAAKH,IAC1BnlF,KAAK4c,MAAMwoE,EAAK78E,GAAKg9E,EAAKH,IAC1BplF,KAAK4c,MAAMyoE,EAAK98E,GAAKi9E,EAAKH,IAE9B,CACF,CACA,MAAMI,EAAO5M,EAAMA,EAAMx1E,OAAS,GAClC,MAAO,CAACoiF,EAAK,GAAIA,EAAK,GAAIA,EAAK,GACjC,CAwCWC,EAAkBR,EAAU13E,EAAOsF,MAAQtF,EAAO0J,IAAM1J,EAAOsF,MAxB1E,SAAmBk+C,GACjB,MAAMxuD,EAAIxC,KAAKkX,IAAI,EAAGlX,KAAK8S,IAAImyE,EAAej0B,IAC9C,IAAK,IAAIruD,EAAI,EAAGA,EAAIqiF,EAAW3hF,OAAS,EAAGV,IAAK,CAC9C,MAAOgjF,EAAIR,EAAIC,EAAIC,GAAML,EAAWriF,IAC7BijF,EAAIN,EAAIC,EAAIC,GAAMR,EAAWriF,EAAI,GACxC,GAAIH,GAAKmjF,GAAMnjF,GAAKojF,EAAI,CACtB,MAAMr9E,GAAK/F,EAAImjF,IAAOC,EAAKD,GAC3B,MAAO,CACL3lF,KAAK4c,MAAMuoE,EAAK58E,GAAK+8E,EAAKH,IAC1BnlF,KAAK4c,MAAMwoE,EAAK78E,GAAKg9E,EAAKH,IAC1BplF,KAAK4c,MAAMyoE,EAAK98E,GAAKi9E,EAAKH,IAE9B,CACF,CACA,MAAMI,EAAOT,EAAWA,EAAW3hF,OAAS,GAC5C,MAAO,CAACoiF,EAAK,GAAIA,EAAK,GAAIA,EAAK,GACjC,CAUSI,CAAUX,EAAU13E,EAAO0J,IACpC,CAGO,SAASq4D,I,IACd/hE,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA8Bu3E,EAC9Be,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAQ,GAER,MAAMC,EAAYv4E,EAAO0J,IAAM+tE,EACzBtxD,EAA4C,GAClD,IAAK,IAAIhxB,EAAI,EAAGA,EAAImjF,EAAOnjF,IAAK,CAC9B,MAAMu6B,EAAQ1vB,EAAOsF,IAAM,GAAMgzE,EAAQ,IAAOC,EAAYv4E,EAAOsF,MAC5DvV,EAAG4pB,EAAGvlB,GAAK09B,EAAapC,EAAO,IAAK1vB,EAAQzM,MAAO,IAC1D4yB,EAAMlxB,KAAK,CAAEy6B,QAAO5jB,MAAO,OAAO/b,KAAK4pB,KAAKvlB,MAC9C,CACA,OAAO+xB,CACT,CCtGA,SAASP,EAASC,GAChB,MAAMC,EAAIjhB,SAASghB,EAAI9xB,MAAM,GAAI,IACjC,MAAO,CAAE+xB,GAAK,GAAM,IAAOA,GAAK,EAAK,IAAU,IAAJA,EAC7C,CAuCO,MAAMs8C,EAAmC,CAC9C,CAAE98D,IAAK,EAAGoE,IAAK6jB,IAAUzhB,MAAO,UAAW8C,MAAO,SAAUwmB,OAAQ,KACpE,CAAE9vB,IAAK,EAAGoE,IAAK,EAAGoC,MAAO,UAAW8C,MAAO,cAAewmB,OAAQ,KAClE,CAAE9vB,IAAK,GAAKoE,IAAK,EAAGoC,MAAO,UAAW8C,MAAO,gBAAiBwmB,OAAQ,KACtE,CAAE9vB,IAAK,GAAKoE,IAAK,GAAKoC,MAAO,UAAW8C,MAAO,kBAAmBwmB,OAAQ,KAC1E,CAAE9vB,IAAK,GAAKoE,IAAK,GAAKoC,MAAO,UAAW8C,MAAO,kBAAmBwmB,OAAQ,KAC1E,CAAE9vB,IAAK,EAAGoE,IAAK,GAAKoC,MAAO,UAAW8C,MAAO,eAAgBwmB,OAAQ,KACrE,CAAE9vB,KAAM,GAAKoE,IAAK,EAAGoC,MAAO,UAAW8C,MAAO,eAAgBwmB,QAAS,KACvE,CAAE9vB,KAAM,GAAKoE,KAAM,GAAKoC,MAAO,UAAW8C,MAAO,kBAAmBwmB,QAAS,KAC7E,CAAE9vB,KAAM,GAAKoE,KAAM,GAAKoC,MAAO,UAAW8C,MAAO,kBAAmBwmB,QAAS,KAC7E,CAAE9vB,KAAM,EAAGoE,KAAM,GAAKoC,MAAO,UAAW8C,MAAO,gBAAiBwmB,QAAS,KACzE,CAAE9vB,KAAM,EAAGoE,KAAM,EAAGoC,MAAO,UAAW8C,MAAO,cAAewmB,QAAS,KACrE,CAAE9vB,KAAK,IAAWoE,KAAM,EAAGoC,MAAO,UAAW8C,MAAO,SAAUwmB,QAAS,MACvE94B,IAAKumE,IAAS,IAAKA,EAAK58C,IAAKL,EAASi9C,EAAI/2D,UAqCrC,SAASgkB,EACd0oD,GAKA,MAAoB,aAJpBhqD,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAqB,YAtBvB,SAA+BgqD,GAE7B,MAAMryD,EAAQi8C,EAAgBruE,QAAQmuE,UAEtC,GAAIsW,GAAQryD,EAAM,GAAGiP,OAAQ,OAAOjP,EAAM,GAAGF,IAC7C,IAAK,IAAI9wB,EAAI,EAAGA,EAAIgxB,EAAMtwB,OAAS,EAAGV,IAAK,CACzC,MAAMixB,EAAOD,EAAMhxB,GACbkxB,EAAOF,EAAMhxB,EAAI,GACvB,GAAIqjF,GAAQnyD,EAAK+O,OAAQ,CACvB,MAAMr6B,GAAKy9E,EAAOpyD,EAAKgP,SAAW/O,EAAK+O,OAAShP,EAAKgP,QACrD,MAAO,CACL5iC,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KACrDzzB,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KACrDzzB,KAAK4c,MAAMgX,EAAKH,IAAI,GAAKlrB,GAAKsrB,EAAKJ,IAAI,GAAKG,EAAKH,IAAI,KAEzD,CACF,CACA,OAAOE,EAAMA,EAAMtwB,OAAS,GAAGowB,GACjC,CAQkCwyD,CAAsBD,GAxCxD,SAA8BA,GAC5B,IAAK,MAAM3V,KAAOT,EAChB,GAAIoW,GAAQ3V,EAAIv9D,IAAK,OAAOu9D,EAAI58C,IAElC,OAAOm8C,EAAgBA,EAAgBvsE,OAAS,GAAGowB,GACrD,CAmCgEyyD,CAAqBF,EACrF,CA4BO,SAAS3oD,EAAU2oD,EAAcjvD,GACtC,OAtBF,SAA0Bvf,GACxB,MAAMwc,EAAIh0B,KAAKC,IAAIuX,GAAKxX,KAAKmmF,MACvBx6D,EAAI,GAAK,EAAI,SAAYqI,GACzBoyD,EACJ,MACI,YAAcz6D,EAAI,aAAeA,EAAI,aAAeA,EAAI,YAAeA,EAAI,YAC7EA,EACA3rB,KAAKgyD,KAAKh+B,EAAIA,GAClB,OAAOxc,GAAK,EAAI,IAAO,EAAI4uE,GAAO,IAAO,EAAIA,EAC/C,CAaSC,CAAiBL,EAAOjvD,EACjC,CAeO,MAAM04C,EAAuB,CAClC,CAAE38D,IAAK,GAAKwG,MAAO,UAAW8C,MAAO,UACrC,CAAEtJ,IAAK,GAAKwG,MAAO,UAAW8C,MAAO,WACrC,CAAEtJ,IAAK,GAAKwG,MAAO,UAAW8C,MAAO,WACrC,CAAEtJ,IAAK,IAAMwG,MAAO,UAAW8C,MAAO,UACtC,CAAEtJ,IAAK,EAAGwG,MAAO,UAAW8C,MAAO,UACnCtS,IAAKumE,IAAS,IAAKA,EAAK58C,IAAKL,EAASi9C,EAAI/2D,UAErC,SAAS8jB,EAAYN,GAC1B,IAAK,MAAMuzC,KAAOZ,EAChB,GAAI3yC,GAAQuzC,EAAIv9D,IAAK,OAAOu9D,EAAI58C,IAElC,OAAOg8C,EAAUA,EAAUpsE,OAAS,GAAGowB,GACzC,CC/FO,SAASqjC,EAAmBn2D,GACjC,MAAsB,QAAfA,GAAuC,QAAfA,GAAuC,QAAfA,CACzD,CAeO,SAASulE,I,IAAkB14D,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAA2Bu3E,EAC3D,MAAO,CAACzqB,EAAYhhD,EAA0BwqE,KAC5C,MAAM5hC,EAAMoY,EAAM,GAIlB,IAAK12D,OAAOi4B,SAASqmB,GAEnB,YADA5oC,EAAM,GAAK,GAGb,QAAwBxO,IAApBg5E,EAASnpD,QAAwBunB,IAAQ4hC,EAASnpD,OAEpD,YADArhB,EAAM,GAAK,GAIb,MAAM4jB,EAAgBglB,EAAM4hC,EAAS3rE,MAAQ2rE,EAASz6D,OAEtD,IAAI9rB,EAAW4pB,EAAWvlB,EACN,eAAhB4L,EAAO+xB,MAA6C,SAApB/xB,EAAOwuB,UAMvB,eAAhBxuB,EAAO+xB,MACRhiC,EAAG4pB,EAAGvlB,GAAK07B,EAAkBJ,EAAQ1vB,EAAO84E,eAAgB94E,EAAOwuB,WAEnEz+B,EAAG4pB,EAAGvlB,GAAK09B,EAAapC,EAAO1vB,GAElC8L,EAAM,GAAK/b,EACX+b,EAAM,GAAK6N,EACX7N,EAAM,GAAK1X,EACX0X,EAAM,GAAK,KAXTA,EAAM,GAAK,EAajB,CAKO,SAAS0sD,EACd1S,EACA3rC,EACAC,EACAC,EACAE,G,IAaIg+B,EAZJ/pB,EAAAA,UAAAA,OAAAA,QAAAA,IAAAA,UAAAA,GAAAA,UAAAA,GAAqB,WAErB,GAAiB,QAAbs3B,EAAoB,OAAOyxB,EAM/B,GAAiB,QAAbzxB,EACF,OAAc,MAAPvrC,EAAcg9D,EAAmB,IAAKA,EAAkBhkF,MAAOgnB,GAIxE,OAAQurC,GACN,IAAK,MACHvN,EAASp+B,EACT,MACF,IAAK,MACHo+B,EAASn+B,EACT,MACF,IAAK,MACHm+B,EAASl+B,EAIb,OAAc,MAAVk+B,EAAuBg/B,EAEpB,CAAExlD,KAAM,aAAc+mD,eAAgBvgC,EAAQ/pB,WACvD,C,GClKIuqD,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqB37E,IAAjB47E,EACH,OAAOA,EAAaC,QAGrB,IAAIC,EAASL,EAAyBE,GAAY,CACjDpkF,GAAIokF,EACJhH,QAAQ,EACRkH,QAAS,CAAC,GAUX,OANAE,EAAoBJ,GAAUK,KAAKF,EAAOD,QAASC,EAAQA,EAAOD,QAASH,GAG3EI,EAAOnH,QAAS,EAGTmH,EAAOD,OACf,CAGAH,EAAoBhkF,EAAIqkF,ElJ5BpBtqF,EAAW,GACfiqF,EAAoBO,EAAI,CAAC//B,EAAQggC,EAAU36D,EAAI46D,KAC9C,IAAGD,EAAH,CAMA,IAAIE,EAAensD,IACnB,IAASp4B,EAAI,EAAGA,EAAIpG,EAAS8G,OAAQV,IAAK,CAGzC,IAFA,IAAKqkF,EAAU36D,EAAI46D,GAAY1qF,EAASoG,GACpCwkF,GAAY,EACPtkF,EAAI,EAAGA,EAAImkF,EAAS3jF,OAAQR,MACpB,EAAXokF,GAAsBC,GAAgBD,IAAah+E,OAAOwvB,KAAK+tD,EAAoBO,GAAG53B,MAAOroD,GAAS0/E,EAAoBO,EAAEjgF,GAAKkgF,EAASnkF,KAC9ImkF,EAASptB,OAAO/2D,IAAK,IAErBskF,GAAY,EACTF,EAAWC,IAAcA,EAAeD,IAG7C,GAAGE,EAAW,CACb5qF,EAASq9D,OAAOj3D,IAAK,GACrB,IAAIpF,EAAI8uB,SACEvhB,IAANvN,IAAiBypD,EAASzpD,EAC/B,CACD,CACA,OAAOypD,CAnBP,CAJCigC,EAAWA,GAAY,EACvB,IAAI,IAAItkF,EAAIpG,EAAS8G,OAAQV,EAAI,GAAKpG,EAASoG,EAAI,GAAG,GAAKskF,EAAUtkF,IAAKpG,EAASoG,GAAKpG,EAASoG,EAAI,GACrGpG,EAASoG,GAAK,CAACqkF,EAAU36D,EAAI46D,ImJJ/BT,EAAoBlzD,EAAKszD,IACxB,IAAIQ,EAASR,GAAUA,EAAOS,WAC7B,IAAOT,EAAiB,QACxB,IAAM,EAEP,OADAJ,EAAoBriF,EAAEijF,EAAQ,CAAEzlF,EAAGylF,IAC5BA,GlJNJ3qF,EAAWwM,OAAOq+E,eAAkBC,GAASt+E,OAAOq+E,eAAeC,GAASA,GAASA,EAAa,UAQtGf,EAAoB76D,EAAI,SAAS/hB,EAAO21B,GAEvC,GADU,EAAPA,IAAU31B,EAAQyuB,KAAKzuB,IAChB,EAAP21B,EAAU,OAAO31B,EACpB,GAAoB,iBAAVA,GAAsBA,EAAO,CACtC,GAAW,EAAP21B,GAAa31B,EAAMy9E,WAAY,OAAOz9E,EAC1C,GAAW,GAAP21B,GAAoC,mBAAf31B,EAAMhM,KAAqB,OAAOgM,CAC5D,CACA,IAAI49E,EAAKv+E,OAAOmF,OAAO,MACvBo4E,EAAoBjpF,EAAEiqF,GACtB,IAAIC,EAAM,CAAC,EACXjrF,EAAiBA,GAAkB,CAAC,KAAMC,EAAS,CAAC,GAAIA,EAAS,IAAKA,EAASA,IAC/E,IAAI,IAAIqmC,EAAiB,EAAPvD,GAAY31B,GAA0B,iBAAXk5B,GAAyC,mBAAXA,MAA4BtmC,EAAey2D,QAAQnwB,GAAUA,EAAUrmC,EAASqmC,GAC1J75B,OAAOy+E,oBAAoB5kD,GAASt4B,QAAS1D,GAAS2gF,EAAI3gF,GAAO,IAAO8C,EAAM9C,IAI/E,OAFA2gF,EAAa,QAAI,IAAM,EACvBjB,EAAoBriF,EAAEqjF,EAAIC,GACnBD,CACR,EmJxBAhB,EAAoBriF,EAAI,CAACwiF,EAASgB,KACjC,IAAI,IAAI7gF,KAAO6gF,EACXnB,EAAoB39E,EAAE8+E,EAAY7gF,KAAS0/E,EAAoB39E,EAAE89E,EAAS7/E,IAC5EmC,OAAO2+E,eAAejB,EAAS7/E,EAAK,CAAE+gF,YAAY,EAAMlgF,IAAKggF,EAAW7gF,MCJ3E0/E,EAAoBj1C,EAAI,CAAC,EAGzBi1C,EAAoBn3E,EAAKy4E,GACjB33E,QAAQ2hB,IAAI7oB,OAAOwvB,KAAK+tD,EAAoBj1C,GAAGrR,OAAO,CAAC6nD,EAAUjhF,KACvE0/E,EAAoBj1C,EAAEzqC,GAAKghF,EAASC,GAC7BA,GACL,KCNJvB,EAAoB3xE,EAAKizE,GAEZA,EAAU,IAAM,CAAC,EAAI,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,uBAAuB,IAAM,wBAAwBA,GAAW,MCF1UtB,EAAoBwB,SAAYF,MCDhCtB,EAAoBr/D,EAAI,WACvB,GAA0B,iBAAflW,WAAyB,OAAOA,WAC3C,IACC,OAAOonB,MAAQ,IAAI4vD,SAAS,cAAb,EAChB,CAAE,MAAO54E,GACR,GAAsB,iBAAXya,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxB08D,EAAoB39E,EAAI,CAAC0+E,EAAKW,IAAUj/E,OAAOk/E,UAAUC,eAAetB,KAAKS,EAAKW,GvJA9ExrF,EAAa,CAAC,EACdC,EAAoB,2BAExB6pF,EAAoBtf,EAAI,CAAC5kE,EAAKqP,EAAM7K,EAAKghF,KACxC,GAAGprF,EAAW4F,GAAQ5F,EAAW4F,GAAKG,KAAKkP,OAA3C,CACA,IAAI02E,EAAQC,EACZ,QAAWx9E,IAARhE,EAEF,IADA,IAAIyhF,EAAUrhF,SAASshF,qBAAqB,UACpC7lF,EAAI,EAAGA,EAAI4lF,EAAQllF,OAAQV,IAAK,CACvC,IAAI4F,EAAIggF,EAAQ5lF,GAChB,GAAG4F,EAAEkgF,aAAa,QAAUnmF,GAAOiG,EAAEkgF,aAAa,iBAAmB9rF,EAAoBmK,EAAK,CAAEuhF,EAAS9/E,EAAG,KAAO,CACpH,CAEG8/E,IACHC,GAAa,GACbD,EAASnhF,SAASma,cAAc,WAEzBqnE,QAAU,QACblC,EAAoBmC,IACvBN,EAAOO,aAAa,QAASpC,EAAoBmC,IAElDN,EAAOO,aAAa,eAAgBjsF,EAAoBmK,GAExDuhF,EAAO3uC,IAAMp3C,GAEd5F,EAAW4F,GAAO,CAACqP,GACnB,IAAIk3E,EAAmB,CAAC1K,EAAM1rE,KAE7B41E,EAAOzI,QAAUyI,EAAO1I,OAAS,KACjC1W,aAAaD,GACb,IAAI8f,EAAUpsF,EAAW4F,GAIzB,UAHO5F,EAAW4F,GAClB+lF,EAAOU,YAAcV,EAAOU,WAAWC,YAAYX,GACnDS,GAAWA,EAAQt+E,QAAS6hB,GAAQA,EAAG5Z,IACpC0rE,EAAM,OAAOA,EAAK1rE,IAElBu2D,EAAU34D,WAAWw4E,EAAiBxxD,KAAK,UAAMvsB,EAAW,CAAExE,KAAM,UAAWy/C,OAAQsiC,IAAW,MACtGA,EAAOzI,QAAUiJ,EAAiBxxD,KAAK,KAAMgxD,EAAOzI,SACpDyI,EAAO1I,OAASkJ,EAAiBxxD,KAAK,KAAMgxD,EAAO1I,QACnD2I,GAAcphF,SAASiM,KAAKqO,YAAY6mE,EAnCkB,GwJH3D7B,EAAoBjpF,EAAKopF,IACH,oBAAXsC,QAA0BA,OAAOC,aAC1CjgF,OAAO2+E,eAAejB,EAASsC,OAAOC,YAAa,CAAEt/E,MAAO,WAE7DX,OAAO2+E,eAAejB,EAAS,aAAc,CAAE/8E,OAAO,KCLvD48E,EAAoB2C,IAAOvC,IAC1BA,EAAOwC,MAAQ,GACVxC,EAAOpyE,WAAUoyE,EAAOpyE,SAAW,IACjCoyE,G,MCHR,IAAIyC,EACA7C,EAAoBr/D,EAAEmiE,gBAAeD,EAAY7C,EAAoBr/D,EAAE4C,SAAW,IACtF,IAAI7iB,EAAWs/E,EAAoBr/D,EAAEjgB,SACrC,IAAKmiF,GAAaniF,IACbA,EAASqiF,eAAkE,WAAjDriF,EAASqiF,cAAc3c,QAAQrzB,gBAC5D8vC,EAAYniF,EAASqiF,cAAc7vC,MAC/B2vC,GAAW,CACf,IAAId,EAAUrhF,EAASshF,qBAAqB,UAC5C,GAAGD,EAAQllF,OAEV,IADA,IAAIV,EAAI4lF,EAAQllF,OAAS,EAClBV,GAAK,KAAO0mF,IAAc,aAAahpF,KAAKgpF,KAAaA,EAAYd,EAAQ5lF,KAAK+2C,GAE3F,CAID,IAAK2vC,EAAW,MAAM,IAAIjhF,MAAM,yDAChCihF,EAAYA,EAAUn8E,QAAQ,SAAU,IAAIA,QAAQ,OAAQ,IAAIA,QAAQ,QAAS,IAAIA,QAAQ,YAAa,KAC1Gs5E,EAAoBzvE,EAAIsyE,C,WCbxB,IAAIG,EAAkB,CACrB,IAAK,GAGNhD,EAAoBj1C,EAAE1uC,EAAI,CAACilF,EAASC,KAElC,IAAI0B,EAAqBjD,EAAoB39E,EAAE2gF,EAAiB1B,GAAW0B,EAAgB1B,QAAWh9E,EACtG,GAA0B,IAAvB2+E,EAGF,GAAGA,EACF1B,EAAStlF,KAAKgnF,EAAmB,QAC3B,CAGL,IAAI93D,EAAU,IAAIxhB,QAAQ,CAACC,EAASkpD,IAAYmwB,EAAqBD,EAAgB1B,GAAW,CAAC13E,EAASkpD,IAC1GyuB,EAAStlF,KAAKgnF,EAAmB,GAAK93D,GAGtC,IAAIrvB,EAAMkkF,EAAoBzvE,EAAIyvE,EAAoB3xE,EAAEizE,GAEpD9+E,EAAQ,IAAIZ,MAgBhBo+E,EAAoBtf,EAAE5kE,EAfFmQ,IACnB,GAAG+zE,EAAoB39E,EAAE2gF,EAAiB1B,KAEf,KAD1B2B,EAAqBD,EAAgB1B,MACR0B,EAAgB1B,QAAWh9E,GACrD2+E,GAAoB,CACtB,IAAIC,EAAYj3E,IAAyB,SAAfA,EAAMnM,KAAkB,UAAYmM,EAAMnM,MAChEqjF,EAAUl3E,GAASA,EAAMszC,QAAUtzC,EAAMszC,OAAOrM,IACpD1wC,EAAMqM,QAAU,iBAAmByyE,EAAU,cAAgB4B,EAAY,KAAOC,EAAU,IAC1F3gF,EAAMU,KAAO,iBACbV,EAAM1C,KAAOojF,EACb1gF,EAAM4F,QAAU+6E,EAChBF,EAAmB,GAAGzgF,EACvB,GAGuC,SAAW8+E,EAASA,EAE/D,GAYHtB,EAAoBO,EAAElkF,EAAKilF,GAA0C,IAA7B0B,EAAgB1B,GAGxD,IAAI8B,EAAuB,CAACC,EAA4B76E,KACvD,IAGIy3E,EAAUqB,GAHTd,EAAU8C,EAAaC,GAAW/6E,EAGhBrM,EAAI,EAC3B,GAAGqkF,EAASzkF,KAAMF,GAAgC,IAAxBmnF,EAAgBnnF,IAAa,CACtD,IAAIokF,KAAYqD,EACZtD,EAAoB39E,EAAEihF,EAAarD,KACrCD,EAAoBhkF,EAAEikF,GAAYqD,EAAYrD,IAGhD,GAAGsD,EAAS,IAAI/iC,EAAS+iC,EAAQvD,EAClC,CAEA,IADGqD,GAA4BA,EAA2B76E,GACrDrM,EAAIqkF,EAAS3jF,OAAQV,IACzBmlF,EAAUd,EAASrkF,GAChB6jF,EAAoB39E,EAAE2gF,EAAiB1B,IAAY0B,EAAgB1B,IACrE0B,EAAgB1B,GAAS,KAE1B0B,EAAgB1B,GAAW,EAE5B,OAAOtB,EAAoBO,EAAE//B,IAG1BgjC,EAAqBC,KAA0C,oCAAIA,KAA0C,qCAAK,GACtHD,EAAmBx/E,QAAQo/E,EAAqBvyD,KAAK,KAAM,IAC3D2yD,EAAmBvnF,KAAOmnF,EAAqBvyD,KAAK,KAAM2yD,EAAmBvnF,KAAK40B,KAAK2yD,G,KCrFvFxD,EAAoBmC,QAAK79E,ECGzB,IAAIo/E,EAAsB1D,EAAoBO,OAAEj8E,EAAW,CAAC,KAAM,IAAO07E,EAAoB,OAC7F0D,EAAsB1D,EAAoBO,EAAEmD,E","sources":["webpack://archx_eemsgeul-frontend/webpack/runtime/chunk loaded","webpack://archx_eemsgeul-frontend/webpack/runtime/create fake namespace object","webpack://archx_eemsgeul-frontend/webpack/runtime/load script","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-omgeving.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/button.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/getal-notatie.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/omgeving.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/google-map-tiles.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/kaart-ondergrond.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/core/params.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/core/serverSentEvents.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/core/pathSerializer.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/core/utils.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/core/auth.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/client/utils.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/client.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/api/client/client.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/auth/api.ts","webpack://archx_eemsgeul-frontend/./frontend/js/auth/AuthProvider.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakHeatmap.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/badge.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/table.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakOverviewTable.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/peilsoort.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/KuberingTable.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/api/sdk.gen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakBaggerhoeveelheden.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-baggerhoeveelheden.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-baggerverwachting.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/PanamaxHull.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/reference-levels.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakCrossSection.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/kaart-experiment.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/tijdlijn-meting.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/timeline-range.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-datasets.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/palet-verandering.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/tile-geometrie.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/bathycog-delta.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/cog-block-cache.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/projections.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/vak-meting.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakMeting.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakWerkgebied.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-werkgebied.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/card.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/analyse-thresholds.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakDetailPanel.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VakkenLijn.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/chart.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/tijdas.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/VolumeTrendChart.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/DepthTimeChart.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-baggervakken.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-latest-datasets.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/trend-data.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/PageHeader.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/PageSection.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/analyse/AnalysePage.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-trend-datasets.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/input.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/separator.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/sheet.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/skeleton.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/tooltip.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/sidebar.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-mobile.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/app-sidebar.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/sonner.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ZandstromenRoute.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/label.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/select.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/DataDatasets.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/DataJobs.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/DataUploads.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/Fase2ComingSoon.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/KaartContext.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-overlays.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-reference-cog.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/cross-section.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/CrossSectionDrawer.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/cross-section-merge.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/MapToolbar.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/CrossSectionTool.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/DiepteSpeld.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/gebied.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/GebiedTool.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/kaart/OmgevingLagen.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/kaart-object.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/depth-at-point.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/dieptecijfers.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-dieptecijfers.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/dieptecontouren.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-dieptecontouren.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/mercator.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/dataset-viewport.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/datum-afstand.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-maand-lagen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-scheepsactiviteit.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/baggervak-mask.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/cog-source.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/geojson-bounds.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/laagkleuren.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/relief.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/scheepsactiviteit.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/shallowest-points.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/speld-anker.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/MapView.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/tile-request-queue.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/kaart-benchmark.ts","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-diepte-op-punt.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/tijdlijn-status.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/slider.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/TimelineBar.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-survey-dates.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/WerkbladGebied.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/pijltoetsen.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/WerkbladKaartbeeld.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/WerkbladLagen.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/WerkbladVak.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/Werkblad.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/kaart/KaartPage.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/hooks/use-plaatmigratie.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/plaatmigratie/FilmVenster.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/plaatmigratie/Tijdbalk.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/plaatmigratie.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/kaart/PlaatmigratieLagen.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/plaatmigratie/PlaatmigratiePage.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/routes/index.ts","webpack://archx_eemsgeul-frontend/./frontend/js/pages/Login.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ProtectedLayout.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/AppShellLayout.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/Plaatmigratie.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/AnalyseRoute.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/DataDatasetDetail.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/pages/DataTrash.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/App.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/index.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/switch.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/display-tiles-protocol.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/utils.ts","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/toggle.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/components/ui/toggle-group.tsx","webpack://archx_eemsgeul-frontend/./frontend/js/lib/palet-diepte.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/palet-verschil.ts","webpack://archx_eemsgeul-frontend/./frontend/js/lib/depth-colors.ts","webpack://archx_eemsgeul-frontend/webpack/bootstrap","webpack://archx_eemsgeul-frontend/webpack/runtime/compat get default export","webpack://archx_eemsgeul-frontend/webpack/runtime/define property getters","webpack://archx_eemsgeul-frontend/webpack/runtime/ensure chunk","webpack://archx_eemsgeul-frontend/webpack/runtime/get javascript chunk filename","webpack://archx_eemsgeul-frontend/webpack/runtime/get mini-css chunk filename","webpack://archx_eemsgeul-frontend/webpack/runtime/global","webpack://archx_eemsgeul-frontend/webpack/runtime/hasOwnProperty shorthand","webpack://archx_eemsgeul-frontend/webpack/runtime/make namespace object","webpack://archx_eemsgeul-frontend/webpack/runtime/node module decorator","webpack://archx_eemsgeul-frontend/webpack/runtime/publicPath","webpack://archx_eemsgeul-frontend/webpack/runtime/jsonp chunk loading","webpack://archx_eemsgeul-frontend/webpack/runtime/nonce","webpack://archx_eemsgeul-frontend/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);\nvar leafPrototypes;\n// create a fake namespace object\n// mode & 1: value is a module id, require it\n// mode & 2: merge all properties of value into the ns\n// mode & 4: return value when already ns object\n// mode & 16: return value when it's Promise-like\n// mode & 8|1: behave like require\n__webpack_require__.t = function(value, mode) {\n\tif(mode & 1) value = this(value);\n\tif(mode & 8) return value;\n\tif(typeof value === 'object' && value) {\n\t\tif((mode & 4) && value.__esModule) return value;\n\t\tif((mode & 16) && typeof value.then === 'function') return value;\n\t}\n\tvar ns = Object.create(null);\n\t__webpack_require__.r(ns);\n\tvar def = {};\n\tleafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];\n\tfor(var current = mode & 2 && value; (typeof current == 'object' || typeof current == 'function') && !~leafPrototypes.indexOf(current); current = getProto(current)) {\n\t\tObject.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key])));\n\t}\n\tdef['default'] = () => (value);\n\t__webpack_require__.d(ns, def);\n\treturn ns;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"archx_eemsgeul-frontend:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = (url, done, key, chunkId) => {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = (prev, event) => {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach((fn) => (fn(event)));\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","import { useEffect, useState } from 'react';\n\nimport type { OmgevingKaart } from '@/js/lib/omgeving';\n\n/** De leeslinks in de lijst verlopen na 60–120 minuten; ruim daarvoor verversen. */\nexport const OMGEVING_TTL_MS = 50 * 60 * 1000;\n\nconst GEEN: OmgevingKaart[] = [];\n\nexport async function haalOmgevingKaarten(): Promise<OmgevingKaart[] | null> {\n  try {\n    const r = await fetch('/api/omgeving/');\n    if (!r.ok) return null;\n    return (await r.json()) as OmgevingKaart[];\n  } catch {\n    return null;\n  }\n}\n\n/**\n * De omgevingskaarten (Omgeving actueel). Een mislukte verversing laat de vorige\n * lijst staan: een lege lijst zou de omgeving laten verdwijnen om een netwerkfout.\n */\nexport function useOmgevingKaarten(enabled: boolean): OmgevingKaart[] {\n  const [kaarten, setKaarten] = useState<OmgevingKaart[]>(GEEN);\n\n  useEffect(() => {\n    if (!enabled) return;\n    let weg = false;\n    const laad = () => {\n      void haalOmgevingKaarten().then((k) => {\n        if (!weg && k) setKaarten(k);\n      });\n    };\n    laad();\n    const timer = setInterval(laad, OMGEVING_TTL_MS);\n    return () => {\n      weg = true;\n      clearInterval(timer);\n    };\n  }, [enabled]);\n\n  return kaarten;\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default: 'h-10 px-4 py-2 has-[>svg]:px-3',\n        xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: 'h-9 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n        lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',\n        icon: 'size-10',\n        'icon-xs': \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm': 'size-8',\n        'icon-lg': 'size-10',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  asChild = false,\n  ...props\n}: React.ComponentProps<'button'> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot.Root : 'button';\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n","/**\n * Getallen in Nederlandse notatie: komma als decimaalteken, punt per duizendtal\n * en een echt minteken (U+2212).\n *\n * `toLocaleString('nl-NL')` geeft een koppelteken als min. In Plex Mono is dat\n * korter dan een cijfer, waardoor een kolom met negatieve diepten niet meer op\n * de komma uitlijnt.\n */\nexport function getal(\n  waarde: number,\n  decimalen = 0,\n  { plusteken = false }: { plusteken?: boolean } = {}\n): string {\n  const tekst = Math.abs(waarde).toLocaleString('nl-NL', {\n    minimumFractionDigits: decimalen,\n    maximumFractionDigits: decimalen,\n  });\n  // Afgerond op nul is nul, zonder teken: \"−0,0\" leest als een meting.\n  if (/^[0.,]+$/.test(tekst)) return tekst;\n  if (waarde < 0) return `−${tekst}`;\n  return plusteken && waarde > 0 ? `+${tekst}` : tekst;\n}\n","/**\n * Omgeving actueel: welke omgevingskaart bij een tijdlijnstap hoort, en hoe je\n * bron, datum en zekerheid in gewone taal zegt (ontwerp 2026-10-14, §5).\n *\n * Nooit vooruit: op een meetstap de nieuwste meting-kaart met klare tegels en\n * datum ≤ de stap; op een voorspellingsstap de voorspellingskaart van die\n * maand. Zo blijft de kaart gevuld als een peiling binnenkomt vóór het\n * rekenscript gedraaid heeft, en staat er nooit \"verwacht\" op een meetstap.\n */\nimport type { ReferenceLevel } from './depth-colors';\n\nexport type OmgevingVariant = 'nap' | 'lat' | 'relief';\nexport type OmgevingSoort = 'meting' | 'voorspelling';\n\nexport interface OmgevingBron {\n  /** `geschat`: klein gat zonder enige meting, glad ingevuld uit de omgeving. */\n  soort: 'geen' | 'loding' | 'peiling' | 'foto' | 'geschat';\n  datum?: string;\n  jaar?: number;\n  vak?: string;\n  nr?: number;\n  /** Maand (JJJJ-MM) vanaf wanneer deze bron meetelt. */\n  sinds?: string;\n  /** Deze bron telt in de maand van de kaart voor het eerst mee. */\n  nieuw?: boolean;\n}\n\nexport interface OmgevingKaart {\n  datum: string;\n  soort: OmgevingSoort;\n  stand_op: string;\n  bbox: number[] | null;\n  bronnen: OmgevingBron[];\n  tegels: Partial<Record<OmgevingVariant, string>>;\n  diepte_url: string;\n  zekerheid_url: string;\n  zandstroom_url: string | null;\n}\n\nexport type NieuweBron = 'foto' | 'loding';\n\nexport const NIEUW_LABEL: Record<NieuweBron, string> = {\n  foto: 'nieuwe foto',\n  loding: 'nieuwe vaklodingen',\n};\n\nconst MAANDEN = [\n  'januari',\n  'februari',\n  'maart',\n  'april',\n  'mei',\n  'juni',\n  'juli',\n  'augustus',\n  'september',\n  'oktober',\n  'november',\n  'december',\n];\n\n/** NAP en LAT tonen de dieptekleuren; op een ontwerpdiepte is de omgeving grijs reliëf. */\nexport function variantVoor(referentie: ReferenceLevel): OmgevingVariant {\n  if (referentie === 'nap') return 'nap';\n  if (referentie === 'lat') return 'lat';\n  return 'relief';\n}\n\nexport function omgevingBronId(kaart: OmgevingKaart, variant: OmgevingVariant): string {\n  return `omgeving-${variant}-${kaart.soort}-${kaart.datum}`;\n}\n\nexport function omgevingVoorDatum(\n  kaarten: OmgevingKaart[],\n  datum: string | null,\n  isVoorspelling: boolean,\n  variant: OmgevingVariant\n): OmgevingKaart | null {\n  if (!datum) return null;\n  const klaar = kaarten.filter((k) => !!k.tegels[variant]);\n  if (isVoorspelling) {\n    const v = klaar.find(\n      (k) => k.soort === 'voorspelling' && k.datum.slice(0, 7) === datum.slice(0, 7)\n    );\n    if (v) return v;\n  }\n  let beste: OmgevingKaart | null = null;\n  for (const k of klaar) {\n    if (k.soort === 'meting' && k.datum <= datum && (!beste || k.datum > beste.datum)) beste = k;\n  }\n  return beste;\n}\n\n/** Een gemonteerde omgevingsbron: kaart, pakket (variant), bron-id en pakket-URL. */\nexport interface Montage {\n  kaart: OmgevingKaart;\n  variant: OmgevingVariant;\n  id: string;\n  url: string;\n}\n\nconst VARIANTEN: OmgevingVariant[] = ['nap', 'lat', 'relief'];\n\nfunction opDatum(a: OmgevingKaart, b: OmgevingKaart): number {\n  return a.datum.localeCompare(b.datum) || a.soort.localeCompare(b.soort);\n}\n\n/**\n * De gekozen maand en zijn buren in deze variant, plus de bron die nu zichtbaar is,\n * ook als die van een ander referentievlak is: bij NAP → NGD blijft de NAP-laag\n * staan tot het reliëf geladen is, anders staat er even een leeg vlak.\n */\nexport function teMonteren(\n  kaarten: OmgevingKaart[],\n  doel: OmgevingKaart | null,\n  zichtbaarId: string | null,\n  variant: OmgevingVariant,\n  vooruit: readonly string[] = []\n): Montage[] {\n  const klaar = kaarten.filter((k) => !!k.tegels[variant]).sort(opDatum);\n  const uit: Montage[] = [];\n  const voeg = (kaart: OmgevingKaart, v: OmgevingVariant) => {\n    const id = omgevingBronId(kaart, v);\n    const url = kaart.tegels[v];\n    if (url && !uit.some((m) => m.id === id)) uit.push({ kaart, variant: v, id, url });\n  };\n  if (doel) {\n    const doelId = omgevingBronId(doel, variant);\n    const i = klaar.findIndex((k) => omgevingBronId(k, variant) === doelId);\n    if (i >= 0) for (const j of [i - 1, i, i + 1]) if (klaar[j]) voeg(klaar[j], variant);\n  }\n  if (zichtbaarId) {\n    for (const k of kaarten)\n      for (const v of VARIANTEN) if (omgevingBronId(k, v) === zichtbaarId) voeg(k, v);\n  }\n  const vooruitIds = new Set(vooruit);\n  for (const k of klaar) if (vooruitIds.has(omgevingBronId(k, variant))) voeg(k, variant);\n  return uit.sort((a, b) => opDatum(a.kaart, b.kaart) || a.variant.localeCompare(b.variant));\n}\n\n/**\n * De volgende `aantal` maanden om op de achtergrond klaar te zetten, zoals de peilingen\n * (eis 1, Jelle 15-10: de slider wisselt direct). Vanaf de gekozen maand naar buiten,\n * bij gelijke afstand eerst vooruit in de tijd; wat al gemonteerd is, slaat hij over.\n */\nexport function volgendeVooruit(\n  kaarten: OmgevingKaart[],\n  doel: OmgevingKaart | null,\n  variant: OmgevingVariant,\n  gemonteerd: ReadonlySet<string>,\n  aantal: number\n): string[] {\n  if (!doel) return [];\n  const klaar = kaarten.filter((k) => !!k.tegels[variant]).sort(opDatum);\n  const doelId = omgevingBronId(doel, variant);\n  const i = klaar.findIndex((k) => omgevingBronId(k, variant) === doelId);\n  if (i < 0) return [];\n  const uit: string[] = [];\n  for (let afstand = 1; afstand < klaar.length && uit.length < aantal; afstand++) {\n    for (const j of [i + afstand, i - afstand]) {\n      const k = klaar[j];\n      if (!k || uit.length >= aantal) continue;\n      const id = omgevingBronId(k, variant);\n      if (!gemonteerd.has(id)) uit.push(id);\n    }\n  }\n  return uit;\n}\n\n/** De vorige omgeving blijft staan tot de nieuwe bron geladen is: geen witte flits. */\nexport function volgendeZichtbaar(\n  huidig: string | null,\n  doelId: string | null,\n  geladen: (id: string) => boolean\n): string | null {\n  if (!doelId) return null;\n  if (!huidig || huidig === doelId || geladen(doelId)) return doelId;\n  return huidig;\n}\n\nexport function datumTekst(iso?: string): string {\n  if (!iso) return '';\n  const [j, m, d] = iso.slice(0, 10).split('-');\n  return `${d}-${m}-${j}`;\n}\n\nexport function maandTekst(iso: string): string {\n  const [j, m] = iso.split('-');\n  return `${MAANDEN[Number(m) - 1]} ${j}`;\n}\n\nexport function bronTekst(bron: OmgevingBron | null | undefined): string | null {\n  if (!bron || bron.soort === 'geen') return null;\n  if (bron.soort === 'geschat') return 'geschat uit de omgeving';\n  if (bron.soort === 'loding') return `loding ${bron.jaar ?? bron.datum?.slice(0, 4) ?? ''}`.trim();\n  return `${bron.soort} ${datumTekst(bron.datum)}`.trim();\n}\n\n/** Herkomst bij de speld, zonder \"± m\": die toets (K2) is niet gehaald (Jelle 15-10). */\nexport function herkomstTekst(bron: OmgevingBron | null, verwacht: boolean): string {\n  const delen: string[] = [];\n  const b = bronTekst(bron);\n  if (b) delen.push(bron?.soort === 'geschat' ? b : `laatst gezien: ${b}`);\n  if (verwacht) delen.push('verwacht');\n  return delen.join(' · ');\n}\n\nexport function legendaTekst(kaart: OmgevingKaart): string {\n  return `Buiten de vakken: omgeving van ${maandTekst(kaart.datum)}${\n    kaart.soort === 'voorspelling' ? ', verwacht' : ''\n  }`;\n}\n\n/** Per maand (JJJJ-MM) de bronnen die daar nieuw meetellen: een sprong is dan geen gebeurtenis op de plaat. */\nexport function nieuweBronnenPerMaand(kaarten: OmgevingKaart[]): Record<string, NieuweBron[]> {\n  const uit: Record<string, NieuweBron[]> = {};\n  for (const k of kaarten) {\n    if (k.soort !== 'meting') continue;\n    const soorten = new Set<NieuweBron>();\n    for (const b of k.bronnen) {\n      if (b.nieuw && (b.soort === 'foto' || b.soort === 'loding')) soorten.add(b.soort);\n    }\n    if (soorten.size) uit[k.datum.slice(0, 7)] = [...soorten].sort();\n  }\n  return uit;\n}\n\n/**\n * De nieuwste gemeten omgeving met klare dieptetegels (NAP), als vaste ondergrond\n * (pagina Zandstromen): tegelpakket en maand in gewone woorden.\n */\nexport function nieuwsteBodem(kaarten: OmgevingKaart[]): { url: string; tekst: string } | null {\n  let beste: OmgevingKaart | null = null;\n  for (const k of kaarten) {\n    if (k.soort === 'meting' && k.tegels.nap && (!beste || k.datum > beste.datum)) beste = k;\n  }\n  return beste?.tegels.nap ? { url: beste.tegels.nap, tekst: maandTekst(beste.datum) } : null;\n}\n","// Ondergrondkaart uit de Google Map Tiles API.\n//\n// De sleutel staat als JSON in de pagina (`json_script` in index.html) en is in\n// Google Cloud beperkt tot ons domein. Tegels vragen om een sessietoken; per\n// ondergrond maken we er één per page-load aan (een sessie is twee weken geldig).\n// Google eist de bronvermelding van het getoonde gebied; die halen we bij het\n// aanmaken op voor het Eemsgebied, want de kaart komt daar niet ver buiten.\n\nimport type { Basemap } from '@/js/pages/kaart/KaartContext';\n\nconst API = 'https://tile.googleapis.com';\n\nconst EEMSGEBIED = { north: 53.75, south: 53.2, east: 7.4, west: 6.2, zoom: 12 };\n\nconst SESSIE: Record<Basemap, object> = {\n  light: { mapType: 'roadmap', styles: [{ stylers: [{ saturation: -100 }, { lightness: 20 }] }] },\n  standaard: { mapType: 'roadmap' },\n  satellite: { mapType: 'satellite' },\n};\n\nexport interface GoogleOndergrond {\n  tileUrl: string;\n  attribution: string;\n}\n\nexport function leesGoogleMapTilesKey(doc: Document = document): string {\n  const el = doc.getElementById('google-map-tiles-key');\n  return el?.textContent ? (JSON.parse(el.textContent) as string) : '';\n}\n\nconst sessies = new Map<Basemap, Promise<GoogleOndergrond>>();\n\nexport function haalGoogleOndergrond(key: string, basemap: Basemap): Promise<GoogleOndergrond> {\n  let sessie = sessies.get(basemap);\n  if (!sessie) {\n    sessie = maakSessie(key, basemap);\n    sessies.set(basemap, sessie);\n  }\n  return sessie;\n}\n\nasync function maakSessie(key: string, basemap: Basemap): Promise<GoogleOndergrond> {\n  const k = encodeURIComponent(key);\n  const sessieAntwoord = await fetch(`${API}/v1/createSession?key=${k}`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ ...SESSIE[basemap], language: 'nl-NL', region: 'NL' }),\n  });\n  if (!sessieAntwoord.ok) {\n    throw new Error(`Google Map Tiles: createSession gaf ${sessieAntwoord.status}`);\n  }\n  const { session } = (await sessieAntwoord.json()) as { session: string };\n  const s = encodeURIComponent(session);\n\n  const { north, south, east, west, zoom } = EEMSGEBIED;\n  const viewport = await fetch(\n    `${API}/tile/v1/viewport?session=${s}&key=${k}` +\n      `&zoom=${zoom}&north=${north}&south=${south}&east=${east}&west=${west}`\n  );\n  if (!viewport.ok) {\n    throw new Error(`Google Map Tiles: viewport gaf ${viewport.status}`);\n  }\n  const { copyright } = (await viewport.json()) as { copyright: string };\n\n  return {\n    tileUrl: `${API}/v1/2dtiles/{z}/{x}/{y}?session=${s}&key=${k}`,\n    attribution: copyright || '&copy; Google',\n  };\n}\n\n/** Alleen voor tests: vergeet de bewaarde sessies. */\nexport function vergeetGoogleSessies(): void {\n  sessies.clear();\n}\n","/**\n * De Google-ondergrond van de kaarten, gedeeld door de kaartpagina en de\n * Plaatmigratiemonitor.\n */\nimport { useEffect, useMemo, useState } from 'react';\n\nimport type { Basemap } from '@/js/pages/kaart/KaartContext';\nimport type { StyleSpecification } from 'maplibre-gl';\n\nimport {\n  haalGoogleOndergrond,\n  leesGoogleMapTilesKey,\n  type GoogleOndergrond,\n} from '@/js/lib/google-map-tiles';\n\n// Ondergrond uit de Google Map Tiles API (zie google-map-tiles.ts). Tot de\n// sessie er is blijft de ondergrond leeg.\nexport function ondergrondStijl(ondergrond: GoogleOndergrond | null): StyleSpecification {\n  if (!ondergrond) return { version: 8, sources: {}, layers: [] };\n  return {\n    version: 8,\n    sources: {\n      basemap: {\n        type: 'raster',\n        tiles: [ondergrond.tileUrl],\n        tileSize: 256,\n        attribution: ondergrond.attribution,\n      },\n    },\n    layers: [{ id: 'basemap', type: 'raster', source: 'basemap' }],\n  };\n}\n\nexport function useGoogleOndergrond(basemap: Basemap): GoogleOndergrond | null {\n  const key = useMemo(() => leesGoogleMapTilesKey(), []);\n  const [ondergrond, setOndergrond] = useState<GoogleOndergrond | null>(null);\n  useEffect(() => {\n    let cancelled = false;\n    haalGoogleOndergrond(key, basemap).then(\n      (o) => {\n        if (!cancelled) setOndergrond(o);\n      },\n      (err: unknown) => console.error('Google-ondergrond niet geladen', err)\n    );\n    return () => {\n      cancelled = true;\n    };\n  }, [key, basemap]);\n  return ondergrond;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\ntype Slot = 'body' | 'headers' | 'path' | 'query';\n\nexport type Field =\n  | {\n      in: Exclude<Slot, 'body'>;\n      /**\n       * Field name. This is the name we want the user to see and use.\n       */\n      key: string;\n      /**\n       * Field mapped name. This is the name we want to use in the request.\n       * If omitted, we use the same value as `key`.\n       */\n      map?: string;\n    }\n  | {\n      in: Extract<Slot, 'body'>;\n      /**\n       * Key isn't required for bodies.\n       */\n      key?: string;\n      map?: string;\n    };\n\nexport interface Fields {\n  allowExtra?: Partial<Record<Slot, boolean>>;\n  args?: ReadonlyArray<Field>;\n}\n\nexport type FieldsConfig = ReadonlyArray<Field | Fields>;\n\nconst extraPrefixesMap: Record<string, Slot> = {\n  $body_: 'body',\n  $headers_: 'headers',\n  $path_: 'path',\n  $query_: 'query',\n};\nconst extraPrefixes = Object.entries(extraPrefixesMap);\n\ntype KeyMap = Map<\n  string,\n  {\n    in: Slot;\n    map?: string;\n  }\n>;\n\nconst buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {\n  if (!map) {\n    map = new Map();\n  }\n\n  for (const config of fields) {\n    if ('in' in config) {\n      if (config.key) {\n        map.set(config.key, {\n          in: config.in,\n          map: config.map,\n        });\n      }\n    } else if (config.args) {\n      buildKeyMap(config.args, map);\n    }\n  }\n\n  return map;\n};\n\ninterface Params {\n  body: unknown;\n  headers: Record<string, unknown>;\n  path: Record<string, unknown>;\n  query: Record<string, unknown>;\n}\n\nconst stripEmptySlots = (params: Params) => {\n  for (const [slot, value] of Object.entries(params)) {\n    if (value && typeof value === 'object' && !Object.keys(value).length) {\n      delete params[slot as Slot];\n    }\n  }\n};\n\nexport const buildClientParams = (\n  args: ReadonlyArray<unknown>,\n  fields: FieldsConfig,\n) => {\n  const params: Params = {\n    body: {},\n    headers: {},\n    path: {},\n    query: {},\n  };\n\n  const map = buildKeyMap(fields);\n\n  let config: FieldsConfig[number] | undefined;\n\n  for (const [index, arg] of args.entries()) {\n    if (fields[index]) {\n      config = fields[index];\n    }\n\n    if (!config) {\n      continue;\n    }\n\n    if ('in' in config) {\n      if (config.key) {\n        const field = map.get(config.key)!;\n        const name = field.map || config.key;\n        (params[field.in] as Record<string, unknown>)[name] = arg;\n      } else {\n        params.body = arg;\n      }\n    } else {\n      for (const [key, value] of Object.entries(arg ?? {})) {\n        const field = map.get(key);\n\n        if (field) {\n          const name = field.map || key;\n          (params[field.in] as Record<string, unknown>)[name] = value;\n        } else {\n          const extra = extraPrefixes.find(([prefix]) =>\n            key.startsWith(prefix),\n          );\n\n          if (extra) {\n            const [prefix, slot] = extra;\n            (params[slot] as Record<string, unknown>)[\n              key.slice(prefix.length)\n            ] = value;\n          } else {\n            for (const [slot, allowed] of Object.entries(\n              config.allowExtra ?? {},\n            )) {\n              if (allowed) {\n                (params[slot as Slot] as Record<string, unknown>)[key] = value;\n                break;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  stripEmptySlots(params);\n\n  return params;\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Config } from './types.gen';\n\nexport type ServerSentEventsOptions<TData = unknown> = Omit<\n  RequestInit,\n  'method'\n> &\n  Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & {\n    /**\n     * Fetch API implementation. You can use this option to provide a custom\n     * fetch instance.\n     *\n     * @default globalThis.fetch\n     */\n    fetch?: typeof fetch;\n    /**\n     * Implementing clients can call request interceptors inside this hook.\n     */\n    onRequest?: (url: string, init: RequestInit) => Promise<Request>;\n    /**\n     * Callback invoked when a network or parsing error occurs during streaming.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @param error The error that occurred.\n     */\n    onSseError?: (error: unknown) => void;\n    /**\n     * Callback invoked when an event is streamed from the server.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @param event Event streamed from the server.\n     * @returns Nothing (void).\n     */\n    onSseEvent?: (event: StreamEvent<TData>) => void;\n    serializedBody?: RequestInit['body'];\n    /**\n     * Default retry delay in milliseconds.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @default 3000\n     */\n    sseDefaultRetryDelay?: number;\n    /**\n     * Maximum number of retry attempts before giving up.\n     */\n    sseMaxRetryAttempts?: number;\n    /**\n     * Maximum retry delay in milliseconds.\n     *\n     * Applies only when exponential backoff is used.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @default 30000\n     */\n    sseMaxRetryDelay?: number;\n    /**\n     * Optional sleep function for retry backoff.\n     *\n     * Defaults to using `setTimeout`.\n     */\n    sseSleepFn?: (ms: number) => Promise<void>;\n    url: string;\n  };\n\nexport interface StreamEvent<TData = unknown> {\n  data: TData;\n  event?: string;\n  id?: string;\n  retry?: number;\n}\n\nexport type ServerSentEventsResult<\n  TData = unknown,\n  TReturn = void,\n  TNext = unknown,\n> = {\n  stream: AsyncGenerator<\n    TData extends Record<string, unknown> ? TData[keyof TData] : TData,\n    TReturn,\n    TNext\n  >;\n};\n\nexport const createSseClient = <TData = unknown>({\n  onRequest,\n  onSseError,\n  onSseEvent,\n  responseTransformer,\n  responseValidator,\n  sseDefaultRetryDelay,\n  sseMaxRetryAttempts,\n  sseMaxRetryDelay,\n  sseSleepFn,\n  url,\n  ...options\n}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {\n  let lastEventId: string | undefined;\n\n  const sleep =\n    sseSleepFn ??\n    ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\n  const createStream = async function* () {\n    let retryDelay: number = sseDefaultRetryDelay ?? 3000;\n    let attempt = 0;\n    const signal = options.signal ?? new AbortController().signal;\n\n    while (true) {\n      if (signal.aborted) break;\n\n      attempt++;\n\n      const headers =\n        options.headers instanceof Headers\n          ? options.headers\n          : new Headers(options.headers as Record<string, string> | undefined);\n\n      if (lastEventId !== undefined) {\n        headers.set('Last-Event-ID', lastEventId);\n      }\n\n      try {\n        const requestInit: RequestInit = {\n          redirect: 'follow',\n          ...options,\n          body: options.serializedBody,\n          headers,\n          signal,\n        };\n        let request = new Request(url, requestInit);\n        if (onRequest) {\n          request = await onRequest(url, requestInit);\n        }\n        // fetch must be assigned here, otherwise it would throw the error:\n        // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n        const _fetch = options.fetch ?? globalThis.fetch;\n        const response = await _fetch(request);\n\n        if (!response.ok)\n          throw new Error(\n            `SSE failed: ${response.status} ${response.statusText}`,\n          );\n\n        if (!response.body) throw new Error('No body in SSE response');\n\n        const reader = response.body\n          .pipeThrough(new TextDecoderStream())\n          .getReader();\n\n        let buffer = '';\n\n        const abortHandler = () => {\n          try {\n            reader.cancel();\n          } catch {\n            // noop\n          }\n        };\n\n        signal.addEventListener('abort', abortHandler);\n\n        try {\n          while (true) {\n            const { done, value } = await reader.read();\n            if (done) break;\n            buffer += value;\n\n            const chunks = buffer.split('\\n\\n');\n            buffer = chunks.pop() ?? '';\n\n            for (const chunk of chunks) {\n              const lines = chunk.split('\\n');\n              const dataLines: Array<string> = [];\n              let eventName: string | undefined;\n\n              for (const line of lines) {\n                if (line.startsWith('data:')) {\n                  dataLines.push(line.replace(/^data:\\s*/, ''));\n                } else if (line.startsWith('event:')) {\n                  eventName = line.replace(/^event:\\s*/, '');\n                } else if (line.startsWith('id:')) {\n                  lastEventId = line.replace(/^id:\\s*/, '');\n                } else if (line.startsWith('retry:')) {\n                  const parsed = Number.parseInt(\n                    line.replace(/^retry:\\s*/, ''),\n                    10,\n                  );\n                  if (!Number.isNaN(parsed)) {\n                    retryDelay = parsed;\n                  }\n                }\n              }\n\n              let data: unknown;\n              let parsedJson = false;\n\n              if (dataLines.length) {\n                const rawData = dataLines.join('\\n');\n                try {\n                  data = JSON.parse(rawData);\n                  parsedJson = true;\n                } catch {\n                  data = rawData;\n                }\n              }\n\n              if (parsedJson) {\n                if (responseValidator) {\n                  await responseValidator(data);\n                }\n\n                if (responseTransformer) {\n                  data = await responseTransformer(data);\n                }\n              }\n\n              onSseEvent?.({\n                data,\n                event: eventName,\n                id: lastEventId,\n                retry: retryDelay,\n              });\n\n              if (dataLines.length) {\n                yield data as any;\n              }\n            }\n          }\n        } finally {\n          signal.removeEventListener('abort', abortHandler);\n          reader.releaseLock();\n        }\n\n        break; // exit loop on normal completion\n      } catch (error) {\n        // connection failed or aborted; retry after delay\n        onSseError?.(error);\n\n        if (\n          sseMaxRetryAttempts !== undefined &&\n          attempt >= sseMaxRetryAttempts\n        ) {\n          break; // stop after firing error\n        }\n\n        // exponential backoff: double retry each attempt, cap at 30s\n        const backoff = Math.min(\n          retryDelay * 2 ** (attempt - 1),\n          sseMaxRetryDelay ?? 30000,\n        );\n        await sleep(backoff);\n      }\n    }\n  };\n\n  const stream = createStream();\n\n  return { stream };\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\ninterface SerializeOptions<T>\n  extends SerializePrimitiveOptions,\n    SerializerOptions<T> {}\n\ninterface SerializePrimitiveOptions {\n  allowReserved?: boolean;\n  name: string;\n}\n\nexport interface SerializerOptions<T> {\n  /**\n   * @default true\n   */\n  explode: boolean;\n  style: T;\n}\n\nexport type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';\nexport type ArraySeparatorStyle = ArrayStyle | MatrixStyle;\ntype MatrixStyle = 'label' | 'matrix' | 'simple';\nexport type ObjectStyle = 'form' | 'deepObject';\ntype ObjectSeparatorStyle = ObjectStyle | MatrixStyle;\n\ninterface SerializePrimitiveParam extends SerializePrimitiveOptions {\n  value: string;\n}\n\nexport const separatorArrayExplode = (style: ArraySeparatorStyle) => {\n  switch (style) {\n    case 'label':\n      return '.';\n    case 'matrix':\n      return ';';\n    case 'simple':\n      return ',';\n    default:\n      return '&';\n  }\n};\n\nexport const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {\n  switch (style) {\n    case 'form':\n      return ',';\n    case 'pipeDelimited':\n      return '|';\n    case 'spaceDelimited':\n      return '%20';\n    default:\n      return ',';\n  }\n};\n\nexport const separatorObjectExplode = (style: ObjectSeparatorStyle) => {\n  switch (style) {\n    case 'label':\n      return '.';\n    case 'matrix':\n      return ';';\n    case 'simple':\n      return ',';\n    default:\n      return '&';\n  }\n};\n\nexport const serializeArrayParam = ({\n  allowReserved,\n  explode,\n  name,\n  style,\n  value,\n}: SerializeOptions<ArraySeparatorStyle> & {\n  value: unknown[];\n}) => {\n  if (!explode) {\n    const joinedValues = (\n      allowReserved ? value : value.map((v) => encodeURIComponent(v as string))\n    ).join(separatorArrayNoExplode(style));\n    switch (style) {\n      case 'label':\n        return `.${joinedValues}`;\n      case 'matrix':\n        return `;${name}=${joinedValues}`;\n      case 'simple':\n        return joinedValues;\n      default:\n        return `${name}=${joinedValues}`;\n    }\n  }\n\n  const separator = separatorArrayExplode(style);\n  const joinedValues = value\n    .map((v) => {\n      if (style === 'label' || style === 'simple') {\n        return allowReserved ? v : encodeURIComponent(v as string);\n      }\n\n      return serializePrimitiveParam({\n        allowReserved,\n        name,\n        value: v as string,\n      });\n    })\n    .join(separator);\n  return style === 'label' || style === 'matrix'\n    ? separator + joinedValues\n    : joinedValues;\n};\n\nexport const serializePrimitiveParam = ({\n  allowReserved,\n  name,\n  value,\n}: SerializePrimitiveParam) => {\n  if (value === undefined || value === null) {\n    return '';\n  }\n\n  if (typeof value === 'object') {\n    throw new Error(\n      'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.',\n    );\n  }\n\n  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;\n};\n\nexport const serializeObjectParam = ({\n  allowReserved,\n  explode,\n  name,\n  style,\n  value,\n  valueOnly,\n}: SerializeOptions<ObjectSeparatorStyle> & {\n  value: Record<string, unknown> | Date;\n  valueOnly?: boolean;\n}) => {\n  if (value instanceof Date) {\n    return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;\n  }\n\n  if (style !== 'deepObject' && !explode) {\n    let values: string[] = [];\n    Object.entries(value).forEach(([key, v]) => {\n      values = [\n        ...values,\n        key,\n        allowReserved ? (v as string) : encodeURIComponent(v as string),\n      ];\n    });\n    const joinedValues = values.join(',');\n    switch (style) {\n      case 'form':\n        return `${name}=${joinedValues}`;\n      case 'label':\n        return `.${joinedValues}`;\n      case 'matrix':\n        return `;${name}=${joinedValues}`;\n      default:\n        return joinedValues;\n    }\n  }\n\n  const separator = separatorObjectExplode(style);\n  const joinedValues = Object.entries(value)\n    .map(([key, v]) =>\n      serializePrimitiveParam({\n        allowReserved,\n        name: style === 'deepObject' ? `${name}[${key}]` : key,\n        value: v as string,\n      }),\n    )\n    .join(separator);\n  return style === 'label' || style === 'matrix'\n    ? separator + joinedValues\n    : joinedValues;\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { BodySerializer, QuerySerializer } from './bodySerializer.gen';\nimport {\n  type ArraySeparatorStyle,\n  serializeArrayParam,\n  serializeObjectParam,\n  serializePrimitiveParam,\n} from './pathSerializer.gen';\n\nexport interface PathSerializer {\n  path: Record<string, unknown>;\n  url: string;\n}\n\nexport const PATH_PARAM_RE = /\\{[^{}]+\\}/g;\n\nexport const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {\n  let url = _url;\n  const matches = _url.match(PATH_PARAM_RE);\n  if (matches) {\n    for (const match of matches) {\n      let explode = false;\n      let name = match.substring(1, match.length - 1);\n      let style: ArraySeparatorStyle = 'simple';\n\n      if (name.endsWith('*')) {\n        explode = true;\n        name = name.substring(0, name.length - 1);\n      }\n\n      if (name.startsWith('.')) {\n        name = name.substring(1);\n        style = 'label';\n      } else if (name.startsWith(';')) {\n        name = name.substring(1);\n        style = 'matrix';\n      }\n\n      const value = path[name];\n\n      if (value === undefined || value === null) {\n        continue;\n      }\n\n      if (Array.isArray(value)) {\n        url = url.replace(\n          match,\n          serializeArrayParam({ explode, name, style, value }),\n        );\n        continue;\n      }\n\n      if (typeof value === 'object') {\n        url = url.replace(\n          match,\n          serializeObjectParam({\n            explode,\n            name,\n            style,\n            value: value as Record<string, unknown>,\n            valueOnly: true,\n          }),\n        );\n        continue;\n      }\n\n      if (style === 'matrix') {\n        url = url.replace(\n          match,\n          `;${serializePrimitiveParam({\n            name,\n            value: value as string,\n          })}`,\n        );\n        continue;\n      }\n\n      const replaceValue = encodeURIComponent(\n        style === 'label' ? `.${value as string}` : (value as string),\n      );\n      url = url.replace(match, replaceValue);\n    }\n  }\n  return url;\n};\n\nexport const getUrl = ({\n  baseUrl,\n  path,\n  query,\n  querySerializer,\n  url: _url,\n}: {\n  baseUrl?: string;\n  path?: Record<string, unknown>;\n  query?: Record<string, unknown>;\n  querySerializer: QuerySerializer;\n  url: string;\n}) => {\n  const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;\n  let url = (baseUrl ?? '') + pathUrl;\n  if (path) {\n    url = defaultPathSerializer({ path, url });\n  }\n  let search = query ? querySerializer(query) : '';\n  if (search.startsWith('?')) {\n    search = search.substring(1);\n  }\n  if (search) {\n    url += `?${search}`;\n  }\n  return url;\n};\n\nexport function getValidRequestBody(options: {\n  body?: unknown;\n  bodySerializer?: BodySerializer | null;\n  serializedBody?: unknown;\n}) {\n  const hasBody = options.body !== undefined;\n  const isSerializedBody = hasBody && options.bodySerializer;\n\n  if (isSerializedBody) {\n    if ('serializedBody' in options) {\n      const hasSerializedBody =\n        options.serializedBody !== undefined && options.serializedBody !== '';\n\n      return hasSerializedBody ? options.serializedBody : null;\n    }\n\n    // not all clients implement a serializedBody property (i.e. client-axios)\n    return options.body !== '' ? options.body : null;\n  }\n\n  // plain/text body\n  if (hasBody) {\n    return options.body;\n  }\n\n  // no body was provided\n  return undefined;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nexport type AuthToken = string | undefined;\n\nexport interface Auth {\n  /**\n   * Which part of the request do we use to send the auth?\n   *\n   * @default 'header'\n   */\n  in?: 'header' | 'query' | 'cookie';\n  /**\n   * Header or query parameter name.\n   *\n   * @default 'Authorization'\n   */\n  name?: string;\n  scheme?: 'basic' | 'bearer';\n  type: 'apiKey' | 'http';\n}\n\nexport const getAuthToken = async (\n  auth: Auth,\n  callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,\n): Promise<string | undefined> => {\n  const token =\n    typeof callback === 'function' ? await callback(auth) : callback;\n\n  if (!token) {\n    return;\n  }\n\n  if (auth.scheme === 'bearer') {\n    return `Bearer ${token}`;\n  }\n\n  if (auth.scheme === 'basic') {\n    return `Basic ${btoa(token)}`;\n  }\n\n  return token;\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport { getAuthToken } from '../core/auth.gen';\nimport type { QuerySerializerOptions } from '../core/bodySerializer.gen';\nimport {\n  serializeArrayParam,\n  serializeObjectParam,\n  serializePrimitiveParam,\n} from '../core/pathSerializer.gen';\nimport { getUrl } from '../core/utils.gen';\nimport type { Client, ClientOptions, Config, RequestOptions } from './types.gen';\n\nexport const createQuerySerializer = <T = unknown>({\n  allowReserved,\n  array,\n  object,\n}: QuerySerializerOptions = {}) => {\n  const querySerializer = (queryParams: T) => {\n    const search: string[] = [];\n    if (queryParams && typeof queryParams === 'object') {\n      for (const name in queryParams) {\n        const value = queryParams[name];\n\n        if (value === undefined || value === null) {\n          continue;\n        }\n\n        if (Array.isArray(value)) {\n          const serializedArray = serializeArrayParam({\n            allowReserved,\n            explode: true,\n            name,\n            style: 'form',\n            value,\n            ...array,\n          });\n          if (serializedArray) search.push(serializedArray);\n        } else if (typeof value === 'object') {\n          const serializedObject = serializeObjectParam({\n            allowReserved,\n            explode: true,\n            name,\n            style: 'deepObject',\n            value: value as Record<string, unknown>,\n            ...object,\n          });\n          if (serializedObject) search.push(serializedObject);\n        } else {\n          const serializedPrimitive = serializePrimitiveParam({\n            allowReserved,\n            name,\n            value: value as string,\n          });\n          if (serializedPrimitive) search.push(serializedPrimitive);\n        }\n      }\n    }\n    return search.join('&');\n  };\n  return querySerializer;\n};\n\nconst checkForExistence = (\n  options: Pick<RequestOptions, 'auth' | 'query'> & {\n    headers: Record<any, unknown>;\n  },\n  name?: string,\n): boolean => {\n  if (!name) {\n    return false;\n  }\n  if (name in options.headers || options.query?.[name]) {\n    return true;\n  }\n  if (\n    'Cookie' in options.headers &&\n    options.headers['Cookie'] &&\n    typeof options.headers['Cookie'] === 'string'\n  ) {\n    return options.headers['Cookie'].includes(`${name}=`);\n  }\n  return false;\n};\n\nexport const setAuthParams = async ({\n  security,\n  ...options\n}: Pick<Required<RequestOptions>, 'security'> &\n  Pick<RequestOptions, 'auth' | 'query'> & {\n    headers: Record<any, unknown>;\n  }) => {\n  for (const auth of security) {\n    if (checkForExistence(options, auth.name)) {\n      continue;\n    }\n    const token = await getAuthToken(auth, options.auth);\n\n    if (!token) {\n      continue;\n    }\n\n    const name = auth.name ?? 'Authorization';\n\n    switch (auth.in) {\n      case 'query':\n        if (!options.query) {\n          options.query = {};\n        }\n        options.query[name] = token;\n        break;\n      case 'cookie': {\n        const value = `${name}=${token}`;\n        if ('Cookie' in options.headers && options.headers['Cookie']) {\n          options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`;\n        } else {\n          options.headers['Cookie'] = value;\n        }\n        break;\n      }\n      case 'header':\n      default:\n        options.headers[name] = token;\n        break;\n    }\n  }\n};\n\nexport const buildUrl: Client['buildUrl'] = (options) => {\n  const instanceBaseUrl = options.axios?.defaults?.baseURL;\n\n  const baseUrl =\n    !!options.baseURL && typeof options.baseURL === 'string'\n      ? options.baseURL\n      : instanceBaseUrl;\n\n  return getUrl({\n    baseUrl: baseUrl as string,\n    path: options.path,\n    // let `paramsSerializer()` handle query params if it exists\n    query: !options.paramsSerializer ? options.query : undefined,\n    querySerializer:\n      typeof options.querySerializer === 'function'\n        ? options.querySerializer\n        : createQuerySerializer(options.querySerializer),\n    url: options.url,\n  });\n};\n\nexport const mergeConfigs = (a: Config, b: Config): Config => {\n  const config = { ...a, ...b };\n  config.headers = mergeHeaders(a.headers, b.headers);\n  return config;\n};\n\n/**\n * Special Axios headers keywords allowing to set headers by request method.\n */\nexport const axiosHeadersKeywords = [\n  'common',\n  'delete',\n  'get',\n  'head',\n  'patch',\n  'post',\n  'put',\n] as const;\n\nexport const mergeHeaders = (\n  ...headers: Array<Required<Config>['headers'] | undefined>\n): Record<any, unknown> => {\n  const mergedHeaders: Record<any, unknown> = {};\n  for (const header of headers) {\n    if (!header || typeof header !== 'object') {\n      continue;\n    }\n\n    const iterator = Object.entries(header);\n\n    for (const [key, value] of iterator) {\n      if (\n        axiosHeadersKeywords.includes(\n          key as (typeof axiosHeadersKeywords)[number],\n        ) &&\n        typeof value === 'object'\n      ) {\n        mergedHeaders[key] = {\n          ...(mergedHeaders[key] as Record<any, unknown>),\n          ...value,\n        };\n      } else if (value === null) {\n        delete mergedHeaders[key];\n      } else if (Array.isArray(value)) {\n        for (const v of value) {\n          // @ts-expect-error\n          mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string];\n        }\n      } else if (value !== undefined) {\n        // assume object headers are meant to be JSON stringified, i.e. their\n        // content value in OpenAPI specification is 'application/json'\n        mergedHeaders[key] =\n          typeof value === 'object' ? JSON.stringify(value) : (value as string);\n      }\n    }\n  }\n  return mergedHeaders;\n};\n\nexport const createConfig = <T extends ClientOptions = ClientOptions>(\n  override: Config<Omit<ClientOptions, keyof T> & T> = {},\n): Config<Omit<ClientOptions, keyof T> & T> => ({\n  ...override,\n});\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport { type ClientOptions, type Config, createClient, createConfig } from './client';\nimport type { ClientOptions as ClientOptions2 } from './types.gen';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions2>());\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { AxiosError, AxiosInstance, RawAxiosRequestHeaders } from 'axios';\nimport axios from 'axios';\n\nimport { createSseClient } from '../core/serverSentEvents.gen';\nimport type { HttpMethod } from '../core/types.gen';\nimport { getValidRequestBody } from '../core/utils.gen';\nimport type { Client, Config, RequestOptions } from './types.gen';\nimport {\n  buildUrl,\n  createConfig,\n  mergeConfigs,\n  mergeHeaders,\n  setAuthParams,\n} from './utils.gen';\n\nexport const createClient = (config: Config = {}): Client => {\n  let _config = mergeConfigs(createConfig(), config);\n\n  let instance: AxiosInstance;\n\n  if (_config.axios && !('Axios' in _config.axios)) {\n    instance = _config.axios;\n  } else {\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const { auth, ...configWithoutAuth } = _config;\n    instance = axios.create(configWithoutAuth);\n  }\n\n  const getConfig = (): Config => ({ ..._config });\n\n  const setConfig = (config: Config): Config => {\n    _config = mergeConfigs(_config, config);\n    instance.defaults = {\n      ...instance.defaults,\n      ..._config,\n      // @ts-expect-error\n      headers: mergeHeaders(instance.defaults.headers, _config.headers),\n    };\n    return getConfig();\n  };\n\n  const beforeRequest = async (options: RequestOptions) => {\n    const opts = {\n      ..._config,\n      ...options,\n      axios: options.axios ?? _config.axios ?? instance,\n      headers: mergeHeaders(_config.headers, options.headers),\n    };\n\n    if (opts.security) {\n      await setAuthParams({\n        ...opts,\n        security: opts.security,\n      });\n    }\n\n    if (opts.requestValidator) {\n      await opts.requestValidator(opts);\n    }\n\n    if (opts.body !== undefined && opts.bodySerializer) {\n      opts.body = opts.bodySerializer(opts.body);\n    }\n\n    const url = buildUrl(opts);\n\n    return { opts, url };\n  };\n\n  // @ts-expect-error\n  const request: Client['request'] = async (options) => {\n    // @ts-expect-error\n    const { opts, url } = await beforeRequest(options);\n    try {\n      // assign Axios here for consistency with fetch\n      const _axios = opts.axios!;\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      const { auth, ...optsWithoutAuth } = opts;\n      const response = await _axios({\n        ...optsWithoutAuth,\n        baseURL: '', // the baseURL is already included in `url`\n        data: getValidRequestBody(opts),\n        headers: opts.headers as RawAxiosRequestHeaders,\n        // let `paramsSerializer()` handle query params if it exists\n        params: opts.paramsSerializer ? opts.query : undefined,\n        url,\n      });\n\n      let { data } = response;\n\n      if (opts.responseType === 'json') {\n        if (opts.responseValidator) {\n          await opts.responseValidator(data);\n        }\n\n        if (opts.responseTransformer) {\n          data = await opts.responseTransformer(data);\n        }\n      }\n\n      return {\n        ...response,\n        data: data ?? {},\n      };\n    } catch (error) {\n      const e = error as AxiosError;\n      if (opts.throwOnError) {\n        throw e;\n      }\n      // @ts-expect-error\n      e.error = e.response?.data ?? {};\n      return e;\n    }\n  };\n\n  const makeMethodFn =\n    (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>\n      request({ ...options, method });\n\n  const makeSseFn =\n    (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {\n      const { opts, url } = await beforeRequest(options);\n      return createSseClient({\n        ...opts,\n        body: opts.body as BodyInit | null | undefined,\n        headers: opts.headers as Record<string, string>,\n        method,\n        // @ts-expect-error\n        signal: opts.signal,\n        url,\n      });\n    };\n\n  return {\n    buildUrl,\n    connect: makeMethodFn('CONNECT'),\n    delete: makeMethodFn('DELETE'),\n    get: makeMethodFn('GET'),\n    getConfig,\n    head: makeMethodFn('HEAD'),\n    instance,\n    options: makeMethodFn('OPTIONS'),\n    patch: makeMethodFn('PATCH'),\n    post: makeMethodFn('POST'),\n    put: makeMethodFn('PUT'),\n    request,\n    setConfig,\n    sse: {\n      connect: makeSseFn('CONNECT'),\n      delete: makeSseFn('DELETE'),\n      get: makeSseFn('GET'),\n      head: makeSseFn('HEAD'),\n      options: makeSseFn('OPTIONS'),\n      patch: makeSseFn('PATCH'),\n      post: makeSseFn('POST'),\n      put: makeSseFn('PUT'),\n      trace: makeSseFn('TRACE'),\n    },\n    trace: makeMethodFn('TRACE'),\n  } as Client;\n};\n","import axios from 'axios';\nimport { parse as cookieParse } from 'cookie';\n\nimport type { AuthUser, AuthErrorResponse } from './types';\n\nconst authClient = axios.create({\n  baseURL: '/_allauth/browser/v1',\n  headers: { 'Content-Type': 'application/json' },\n  withCredentials: true,\n});\n\nauthClient.interceptors.request.use((request) => {\n  const { csrftoken } = cookieParse(document.cookie);\n  if (request.headers && csrftoken) {\n    request.headers['X-CSRFTOKEN'] = csrftoken;\n  }\n  return request;\n});\n\nexport async function getSession(): Promise<AuthUser | null> {\n  try {\n    const res = await authClient.get('/auth/session');\n    return res.data?.data?.user ?? null;\n  } catch {\n    return null;\n  }\n}\n\nexport async function login(email: string, password: string): Promise<AuthUser> {\n  try {\n    const res = await authClient.post('/auth/login', { email, password });\n    return res.data.data.user;\n  } catch (error) {\n    if (axios.isAxiosError(error)) {\n      if (error.response?.status === 409) {\n        const sessionUser = await getSession();\n        if (sessionUser) {\n          return sessionUser;\n        }\n      }\n      if (error.response?.data) {\n        const body = error.response.data as AuthErrorResponse;\n        if (body.errors?.length) {\n          throw new Error(body.errors[0].message);\n        }\n      }\n    }\n    throw new Error('Inloggen mislukt. Probeer het opnieuw.');\n  }\n}\n\nexport async function logout(): Promise<void> {\n  try {\n    await authClient.delete('/auth/session');\n  } catch (error) {\n    // allauth destroys the session then returns 401 (\"no longer authenticated\")\n    // — that's the expected success response for logout\n    if (!axios.isAxiosError(error) || error.response?.status !== 401) {\n      throw error;\n    }\n  }\n}\n","import { createContext, useContext, useState, useEffect, useCallback } from 'react';\n\nimport * as authApi from './api';\n\nimport type { AuthUser } from './types';\nimport type { ReactNode } from 'react';\n\ninterface AuthContextValue {\n  user: AuthUser | null;\n  isLoading: boolean;\n  login: (email: string, password: string) => Promise<void>;\n  logout: () => Promise<void>;\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\nexport function AuthProvider({ children }: { children: ReactNode }) {\n  const [user, setUser] = useState<AuthUser | null>(null);\n  const [isLoading, setIsLoading] = useState(true);\n\n  useEffect(() => {\n    authApi.getSession().then((u) => {\n      setUser(u);\n      setIsLoading(false);\n    });\n  }, []);\n\n  const login = useCallback(async (email: string, password: string) => {\n    const u = await authApi.login(email, password);\n    setUser(u);\n  }, []);\n\n  const logout = useCallback(async () => {\n    await authApi.logout();\n    setUser(null);\n  }, []);\n\n  return (\n    <AuthContext.Provider value={{ user, isLoading, login, logout }}>\n      {children}\n    </AuthContext.Provider>\n  );\n}\n\nexport function useAuth(): AuthContextValue {\n  const ctx = useContext(AuthContext);\n  if (!ctx) {\n    throw new Error('useAuth must be used within an AuthProvider');\n  }\n  return ctx;\n}\n","/**\n * Spatiale heatmap binnen de baggervak-vorm.\n *\n * Tekent een SVG met (1) de polygon-outline gedraaid zodat de lange OBB-as\n * horizontaal ligt en (2) een grid van `cols × rows` cellen ge-clipt op die\n * polygon. Cellen krijgen kleur op basis van hun heatmap-categorie:\n *   - null → niet gerenderd (cell-centroid buiten polygon)\n *   - 0 → blauw (dieper dan de maximale diepte: te diep)\n *   - 1 → groen (tussen de maximale diepte en NGD: in orde)\n *   - 2 → groen, als 1: was \"boven IVD\". IVD staat niet in het bestek 2026; de\n *     backend maakt deze categorie niet meer, maar oudere peilingen hebben\n *     hem nog. Een cel boven IVD ligt boven de maximale diepte en onder NGD,\n *     dus hij hoort bij 1. Zo klopt het beeld zonder herberekening.\n *   - 3 → rood (boven NGD: te ondiep)\n *\n * In het bestek 2026 is de maximale waterbodemdiepte \"de diepte die niet\n * overschreden mag worden\": dieper dan max. is dus net zo goed een\n * overschrijding als boven NGD, en krijgt een eigen kleur. Alleen de strook\n * ertussen is in orde.\n *\n * Elke cel toont de diepte op zijn middelpunt. Het is een overzicht van het\n * vak, geen opsomming van elke overschrijding: een kleine plek kan tussen twee\n * middelpunten vallen. Het oppervlak staat in de tabel.\n *\n * `polygon` mag in elke CRS staan; coördinaten worden alleen gebruikt om\n * een lokaal viewBox te bepalen en de outline te tekenen.\n */\n\ninterface Size {\n  width: number;\n  height: number;\n}\n\nexport interface VakHeatmapProps {\n  polygon: GeoJSON.Polygon | GeoJSON.MultiPolygon;\n  cells: ReadonlyArray<number | null>;\n  cols: number;\n  rows: number;\n  /** OBB-rotatiehoek in graden (lange-as → +x). */\n  angleDeg: number;\n  /** OBB-lange-as breedte in meter. */\n  widthM: number;\n  /** OBB-korte-as hoogte in meter. */\n  heightM: number;\n  size: Size;\n}\n\nconst CATEGORY_COLOR: Record<0 | 1 | 2 | 3, string> = {\n  0: '#2563eb', // blauw: te diep\n  1: '#22c55e', // groen: in orde\n  2: '#22c55e', // groen, zie boven\n  3: '#ef4444', // rood: te ondiep\n};\n\n/** De legenda bij de heatmap: één regel per kleur. */\nexport const HEATMAP_LEGENDA: ReadonlyArray<{ color: string; label: string }> = [\n  { color: CATEGORY_COLOR[3], label: 'boven NGD (te ondiep)' },\n  { color: CATEGORY_COLOR[1], label: 'tussen NGD en max. (in orde)' },\n  { color: CATEGORY_COLOR[0], label: 'dieper dan max. (te diep)' },\n];\n\nfunction polygonRings(geom: GeoJSON.Polygon | GeoJSON.MultiPolygon): GeoJSON.Position[][] {\n  return geom.type === 'Polygon' ? geom.coordinates : geom.coordinates.flat();\n}\n\n/**\n * Roteer (x, y) om (cx, cy) over `-angleRad` radialen. We draaien negatief\n * zodat een polygon waarvan de lange as op `angleDeg` lag, terechtkomt met\n * lange as op +x (OBB-local frame).\n */\nfunction toObbLocal(\n  x: number,\n  y: number,\n  cx: number,\n  cy: number,\n  angleRad: number\n): [number, number] {\n  const dx = x - cx;\n  const dy = y - cy;\n  const cos = Math.cos(-angleRad);\n  const sin = Math.sin(-angleRad);\n  return [dx * cos - dy * sin, dx * sin + dy * cos];\n}\n\nexport function VakHeatmap({\n  polygon,\n  cells,\n  cols,\n  rows,\n  angleDeg,\n  widthM,\n  heightM,\n  size,\n}: VakHeatmapProps) {\n  if (cells.length === 0) {\n    return (\n      <svg\n        width={size.width}\n        height={size.height}\n        viewBox={`0 0 ${size.width} ${size.height}`}\n        role=\"img\"\n        aria-label=\"Geen heatmap-data\"\n      />\n    );\n  }\n\n  // We werken volledig in OBB-local frame (lange as = +x, gecentreerd op 0,0).\n  // Zo zitten outline én cellen in hetzelfde coordinatensysteem en mappen we\n  // beide met dezelfde `toSvg` naar canvas-pixels.\n  const rings = polygonRings(polygon);\n  const allPoints = rings.flat();\n  const xs = allPoints.map((p) => p[0]);\n  const ys = allPoints.map((p) => p[1]);\n  const cx = (Math.min(...xs) + Math.max(...xs)) / 2;\n  const cy = (Math.min(...ys) + Math.max(...ys)) / 2;\n  const angleRad = (angleDeg * Math.PI) / 180;\n\n  const ringsLocal = rings.map((ring) => ring.map(([x, y]) => toObbLocal(x, y, cx, cy, angleRad)));\n\n  // Schaal op basis van OBB-extents (widthM × heightM), gecentreerd op (0,0).\n  const margin = 4;\n  const innerWidth = size.width - 2 * margin;\n  const innerHeight = size.height - 2 * margin;\n  const scaleX = innerWidth / widthM;\n  const scaleY = innerHeight / heightM;\n  const scale = Math.min(scaleX, scaleY);\n\n  const toSvg = ([x, y]: [number, number]): [number, number] => [\n    margin + innerWidth / 2 + x * scale,\n    margin + innerHeight / 2 - y * scale, // SVG y-down: flip\n  ];\n\n  const outlinePath = ringsLocal\n    .map(\n      (ring) =>\n        ring\n          .map((pt, i) => {\n            const [sx, sy] = toSvg(pt);\n            return `${i === 0 ? 'M' : 'L'}${sx.toFixed(2)} ${sy.toFixed(2)}`;\n          })\n          .join(' ') + ' Z'\n    )\n    .join(' ');\n\n  const dx = widthM / cols;\n  const dy = heightM / rows;\n\n  return (\n    <svg\n      className=\"h-auto w-full\"\n      style={{ maxWidth: size.width }}\n      viewBox={`0 0 ${size.width} ${size.height}`}\n      role=\"img\"\n      aria-label={`Heatmap ${cols}×${rows}`}\n    >\n      <path d={outlinePath} fill=\"none\" stroke=\"#475569\" strokeWidth={0.5} />\n      {cells.map((value, idx) => {\n        if (value == null) return null;\n        const col = idx % cols;\n        const row = Math.floor(idx / cols);\n        // Cell-centroid direct in OBB-local frame, zelfde frame als outline.\n        const localX = (col + 0.5) * dx - widthM / 2;\n        const localY = (row + 0.5) * dy - heightM / 2;\n        const [sx, sy] = toSvg([localX, localY]);\n        const cellW = dx * scale;\n        const cellH = dy * scale;\n        const color = CATEGORY_COLOR[value as 0 | 1 | 2 | 3] ?? '#cbd5e1';\n        return (\n          <rect\n            key={idx}\n            data-cell={value}\n            x={sx - cellW / 2}\n            y={sy - cellH / 2}\n            width={cellW}\n            height={cellH}\n            fill={color}\n            fillOpacity={0.85}\n          />\n        );\n      })}\n    </svg>\n  );\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nconst badgeVariants = cva(\n  'inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90',\n        secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',\n        destructive:\n          'bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\n        ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 [a&]:hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  asChild = false,\n  ...props\n}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'span';\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  );\n}\n\nexport { Badge, badgeVariants };\n","import * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div data-slot=\"table-container\" className=\"relative w-full overflow-x-auto\">\n      <table\n        data-slot=\"table\"\n        className={cn('w-full caption-bottom text-sm', className)}\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return <thead data-slot=\"table-header\" className={cn('[&_tr]:border-b', className)} {...props} />;\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      data-slot=\"table-body\"\n      className={cn('[&_tr:last-child]:border-0', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      data-slot=\"table-footer\"\n      className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      data-slot=\"table-row\"\n      className={cn(\n        'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      data-slot=\"table-head\"\n      className={cn(\n        'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      data-slot=\"table-cell\"\n      className={cn(\n        'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      data-slot=\"table-caption\"\n      className={cn('text-muted-foreground mt-4 text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';\nimport { useMemo, useState } from 'react';\n\nimport { VakHeatmap } from './VakHeatmap';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\nimport { Badge } from '@/js/components/ui/badge';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/js/components/ui/table';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { cn } from '@/js/lib/utils';\n\nexport interface VakRowInput {\n  code: string;\n  polygon: GeoJSON.Polygon | GeoJSON.MultiPolygon;\n  angleDeg: number;\n  widthM: number;\n  heightM: number;\n  /** De laatste niet-ATP `ready` SurveyDataset voor dit vak, of null als die ontbreekt. */\n  dataset: SurveyDataset | null;\n}\n\ninterface VakOverviewTableProps {\n  rows: ReadonlyArray<VakRowInput>;\n  selectedCode: string | null;\n  onSelect: (code: string) => void;\n}\n\ntype SortKey =\n  | 'code'\n  | 'survey_date'\n  | 'volume_above_ngd_m3'\n  | 'volume_above_ohd_m3'\n  | 'surface_ratio_above_ngd'\n  | 'surface_ratio_below_ohd'\n  | 'cm_above_ngd'\n  | 'depth_max_in_vak'\n  | 'depth_max_incl_randstrook';\ntype SortDir = 'asc' | 'desc';\n\nfunction formatNumber(value: number | null | undefined, digits: number): string {\n  if (value == null) return '—';\n  return getal(value, digits);\n}\n\n/**\n * Uitleg bij de twee kolommen \"ondiepste\". De vakpolygoon is de vaargeul\n * zonder de randstrook van 5 meter, dus het getal dat er altijd al stond is\n * het ondiepste punt *exclusief* die zone. RWS wil beide zien (bevindingen\n * 16-06-2026): de randen tellen niet voor de doorvaart, wel voor het\n * baggerwerk.\n */\nexport const ONDIEPSTE_EXCL_UITLEG =\n  'Ondiepste punt binnen het baggervak, zonder de randstrook van 5 meter langs de rand. Dit is de diepte die telt voor de doorvaart.';\nexport const ONDIEPSTE_INCL_UITLEG =\n  'Ondiepste punt binnen het baggervak mét de randstrook van 5 meter erbij. De randen liggen doorgaans ondieper en tellen mee voor het baggerwerk.';\n\nfunction formatPercent(value: number | null | undefined): string {\n  if (value == null) return '—';\n  return `${getal(value * 100, 2)}%`;\n}\n\n/**\n * De toename (+) of afname (−) van een volume sinds de vorige peiling, klein\n * onder het volume zelf. Het bestek 2026 (art. 32.3.2 lid 2) vraagt die per\n * vak; een eigen kolom zou de tabel twee kolommen breder maken voor een getal\n * dat bij het volume hoort. Leeg zonder vorige peiling.\n */\nexport function VeranderingTovVorige({\n  delta,\n  vorigeDatum,\n  className,\n}: {\n  delta: number | null | undefined;\n  vorigeDatum: string | null | undefined;\n  className?: string;\n}) {\n  if (delta == null) return null;\n  const datum = vorigeDatum\n    ? new Date(`${vorigeDatum}T00:00:00`).toLocaleDateString('nl-NL')\n    : null;\n  // Zichtbaar wat het getal is, niet alleen in een tooltip: op een\n  // touchscreen of op papier is die er niet.\n  return (\n    <span\n      data-testid=\"verandering\"\n      className={cn('getal block text-xs text-muted-foreground', className)}\n      title={\n        datum\n          ? `t.o.v. de periodieke peiling van ${datum}; een aantoonpeiling telt niet als vorige`\n          : undefined\n      }\n    >\n      {getal(delta, 0, { plusteken: true })}\n      {datum && ` sinds ${datum}`}\n    </span>\n  );\n}\n\nexport function VakOverviewTable({ rows, selectedCode, onSelect }: VakOverviewTableProps) {\n  const [sortKey, setSortKey] = useState<SortKey>('volume_above_ngd_m3');\n  const [sortDir, setSortDir] = useState<SortDir>('desc');\n\n  const sortedRows = useMemo(() => {\n    const dir = sortDir === 'asc' ? 1 : -1;\n    return [...rows].sort((a, b) => {\n      if (sortKey === 'code') {\n        return a.code.localeCompare(b.code) * dir;\n      }\n      if (sortKey === 'survey_date') {\n        return (a.dataset?.survey_date ?? '').localeCompare(b.dataset?.survey_date ?? '') * dir;\n      }\n      const av = a.dataset?.[sortKey as keyof SurveyDataset] as number | null | undefined;\n      const bv = b.dataset?.[sortKey as keyof SurveyDataset] as number | null | undefined;\n      if (av == null && bv == null) return 0;\n      if (av == null) return 1; // nulls naar onderen\n      if (bv == null) return -1;\n      return (av - bv) * dir;\n    });\n  }, [rows, sortKey, sortDir]);\n\n  const toggleSort = (key: SortKey) => {\n    if (sortKey === key) {\n      setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));\n    } else {\n      setSortKey(key);\n      setSortDir(key === 'code' ? 'asc' : 'desc');\n    }\n  };\n\n  return (\n    <div className=\"rounded-md border\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <SortableHeader\n              label=\"Vak\"\n              colKey=\"code\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"Peiling\"\n              colKey=\"survey_date\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"m³ boven NGD\"\n              colKey=\"volume_above_ngd_m3\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"m³ boven max.\"\n              colKey=\"volume_above_ohd_m3\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"% boven NGD\"\n              colKey=\"surface_ratio_above_ngd\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"% dieper dan max.\"\n              colKey=\"surface_ratio_below_ohd\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"cm boven NGD\"\n              colKey=\"cm_above_ngd\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"Ondiepste, zonder 5 m\"\n              title={ONDIEPSTE_EXCL_UITLEG}\n              colKey=\"depth_max_in_vak\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <SortableHeader\n              label=\"met 5 m\"\n              title={ONDIEPSTE_INCL_UITLEG}\n              colKey=\"depth_max_incl_randstrook\"\n              align=\"right\"\n              sortKey={sortKey}\n              sortDir={sortDir}\n              toggleSort={toggleSort}\n            />\n            <TableHead\n              style={{ width: 256 }}\n              className=\"text-xs font-semibold text-muted-foreground\"\n            >\n              Verdeling\n            </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {sortedRows.map((row) => {\n            const ds = row.dataset;\n            const isSelected = row.code === selectedCode;\n            const ngdM3 = ds?.volume_above_ngd_m3 ?? null;\n            // Op de afgeronde waarde, anders staat er een rode \"0\".\n            const ngdAfgerond = ngdM3 == null ? null : Math.round(ngdM3);\n            return (\n              <TableRow\n                key={row.code}\n                data-selected={isSelected || undefined}\n                className={cn(\n                  'cursor-pointer hover:bg-muted/40',\n                  isSelected && 'bg-gekozen shadow-[inset_3px_0_0_var(--primary)] hover:bg-gekozen'\n                )}\n                onClick={() => onSelect(row.code)}\n              >\n                <TableCell className=\"font-mono font-medium\">{row.code}</TableCell>\n                <PeildatumCell\n                  date={ds?.survey_date ?? null}\n                  gemeten={ds?.surface_ratio_measured ?? null}\n                />\n                {/* Alleen boven de NGD is rood: dat is de norm. De maximale\n                    diepte is een stap in het onderhoud en blijft een gewoon\n                    getal. */}\n                <TableCell className=\"text-right\">\n                  <span\n                    className={cn(\n                      'getal',\n                      ngdAfgerond != null && ngdAfgerond > 0\n                        ? 'text-destructive'\n                        : 'text-muted-foreground',\n                      ngdAfgerond != null && ngdAfgerond >= 100 && 'font-medium'\n                    )}\n                  >\n                    {formatNumber(ngdM3, 0)}\n                  </span>\n                  <VeranderingTovVorige\n                    delta={ds?.delta_volume_above_ngd_m3}\n                    vorigeDatum={ds?.vorige_peiling_datum}\n                  />\n                </TableCell>\n                <TableCell className=\"text-right\">\n                  <span className=\"getal text-leisteen\">\n                    {formatNumber(ds?.volume_above_ohd_m3, 0)}\n                  </span>\n                  <VeranderingTovVorige\n                    delta={ds?.delta_volume_above_ohd_m3}\n                    vorigeDatum={ds?.vorige_peiling_datum}\n                  />\n                </TableCell>\n                <TableCell className=\"getal text-right text-leisteen\">\n                  {formatPercent(ds?.surface_ratio_above_ngd)}\n                </TableCell>\n                <TableCell className=\"getal text-right text-leisteen\">\n                  {formatPercent(ds?.surface_ratio_below_ohd)}\n                </TableCell>\n                <TableCell className=\"text-right\">\n                  <CmBadge\n                    cm={ds?.cm_above_ngd ?? null}\n                    verouderd={ds != null && isPeilingVerouderd(ds.survey_date)}\n                  />\n                </TableCell>\n                <TableCell className=\"getal text-right text-leisteen\">\n                  {formatNumber(ds?.depth_max_in_vak, 2)}\n                </TableCell>\n                <TableCell className=\"getal text-right text-leisteen\" title={ONDIEPSTE_INCL_UITLEG}>\n                  {formatNumber(ds?.depth_max_incl_randstrook, 2)}\n                </TableCell>\n                <TableCell style={{ width: 256 }}>\n                  <VakHeatmap\n                    polygon={row.polygon}\n                    cells={(ds?.heatmap_cells as Array<number | null> | null) ?? []}\n                    cols={30}\n                    rows={12}\n                    angleDeg={row.angleDeg}\n                    widthM={row.widthM}\n                    heightM={row.heightM}\n                    size={{ width: 240, height: 48 }}\n                  />\n                </TableCell>\n              </TableRow>\n            );\n          })}\n        </TableBody>\n      </Table>\n    </div>\n  );\n}\n\ninterface SortableHeaderProps {\n  label: string;\n  /** Uitleg bij de kolom, als tooltip op de kop. */\n  title?: string;\n  colKey: SortKey;\n  align?: 'left' | 'right';\n  sortKey: SortKey;\n  sortDir: SortDir;\n  toggleSort: (k: SortKey) => void;\n}\nfunction SortableHeader({\n  label,\n  title,\n  colKey,\n  align,\n  sortKey,\n  sortDir,\n  toggleSort,\n}: SortableHeaderProps) {\n  const active = sortKey === colKey;\n  const Icon = !active ? ArrowUpDown : sortDir === 'asc' ? ArrowUp : ArrowDown;\n  return (\n    <TableHead\n      onClick={() => toggleSort(colKey)}\n      title={title}\n      className={cn(\n        'cursor-pointer text-xs font-semibold text-muted-foreground select-none',\n        align === 'right' && 'text-right'\n      )}\n    >\n      <span className=\"inline-flex items-center gap-1\">\n        {label}\n        <Icon className=\"h-3 w-3 text-muted-foreground/60\" />\n      </span>\n    </TableHead>\n  );\n}\n\n/**\n * De cm t.o.v. NGD zoals hij er staat (één decimaal). De kleur volgt dit getal,\n * niet de ruwe waarde: een roze vlak bij \"0,0\" spreekt zichzelf tegen.\n */\nexport function cmAfgerond(cm: number): number {\n  return Math.round(cm * 10) / 10;\n}\n\n/**\n * Het ondiepste punt t.o.v. NGD als getinte badge: hoe sterker de tint, hoe\n * verder boven de norm. Een verouderde peiling krijgt geen rood; op een\n * oude peiling stuur je niet, dus het getal mag ook niet om aandacht vragen.\n */\nexport function cmBadgeKlasse(cm: number | null, verouderd: boolean): string {\n  if (cm == null) return 'bg-transparent text-muted-foreground';\n  if (verouderd) return 'bg-muted text-muted-foreground';\n  if (cmAfgerond(cm) > 50) return 'bg-[#FBE4E1] text-destructive';\n  if (cmAfgerond(cm) > 0) return 'bg-[#FDF1EF] text-destructive';\n  return 'bg-transparent text-leisteen';\n}\n\nfunction CmBadge({ cm, verouderd }: { cm: number | null; verouderd: boolean }) {\n  return (\n    <span\n      data-testid=\"cm-boven-ngd\"\n      className={cn(\n        'getal inline-block min-w-14 rounded-md px-2 py-0.5 text-right',\n        cmBadgeKlasse(cm, verouderd)\n      )}\n    >\n      {cm == null ? '—' : getal(cm, 1, { plusteken: true })}\n    </span>\n  );\n}\n\n/** Na zoveel dagen is de laatste peiling te oud om op te sturen. */\nexport const PEILING_VEROUDERD_DAGEN = 120;\n\n/** Of een peildatum (YYYY-MM-DD) ouder is dan {@link PEILING_VEROUDERD_DAGEN}. */\nexport function isPeilingVerouderd(date: string | null | undefined): boolean {\n  if (!date) return true;\n  const dag = new Date(`${date}T00:00:00`);\n  return (Date.now() - dag.getTime()) / 86_400_000 > PEILING_VEROUDERD_DAGEN;\n}\n\n/**\n * De datum van de peiling waar de rij op rust. Zonder die kolom stond een vak\n * met een peiling van anderhalf jaar oud bovenaan, alsof het de grootste\n * opgave van nu was.\n */\n/**\n * Onder dit aandeel gemeten zegt de peilingcel hoeveel van het vak gemeten is.\n * De percentages in de tabel zijn van het hele vak; bij H (rond 70 %) en een\n * halve peiling van F zou dat anders ongemerkt te laag uitvallen.\n */\nconst GEMETEN_MELDEN_ONDER = 0.99;\n\nfunction PeildatumCell({ date, gemeten }: { date: string | null; gemeten: number | null }) {\n  if (!date) return <TableCell className=\"text-muted-foreground\">—</TableCell>;\n  const dag = new Date(`${date}T00:00:00`);\n  const verouderd = isPeilingVerouderd(date);\n  return (\n    <TableCell\n      className=\"whitespace-nowrap text-leisteen tabular-nums\"\n      title={verouderd ? `Ouder dan ${PEILING_VEROUDERD_DAGEN} dagen` : undefined}\n    >\n      {dag.toLocaleDateString('nl-NL')}\n      {verouderd && (\n        <Badge variant=\"secondary\" className=\"ml-2 rounded-[5px] px-1.5 text-muted-foreground\">\n          verouderd\n        </Badge>\n      )}\n      {gemeten != null && gemeten < GEMETEN_MELDEN_ONDER && (\n        <span\n          data-testid=\"gemeten\"\n          className=\"block text-xs text-muted-foreground\"\n          title=\"Deel van het vak met een meting. De percentages zijn van het hele vak; wat niet gemeten is, telt daarin niet als overschrijding.\"\n        >\n          {getal(gemeten * 100, 0)}% gemeten\n        </span>\n      )}\n    </TableCell>\n  );\n}\n","import type { PeilsoortEnum, SurveyDataset } from '@/js/api/types.gen';\n\n/**\n * Peilsoort: het onderscheid tussen een reguliere peiling en een\n * aantoonpeiling. Een aantoonpeiling volgt op een baggercampagne en dekt\n * alleen het gebaggerde deel van een vak, dus hij hoort niet mee te tellen in\n * vak-brede statistiek of in de zaagtandgrafiek. Zie #113.\n *\n * De werkpeiling ontbreekt bewust: er is nog geen kenmerk in de aanlevering\n * waaraan die te herkennen valt.\n */\n\nexport const PEILSOORT_LABELS: Record<PeilsoortEnum, string> = {\n  regulier: 'Periodieke peiling',\n  atp: 'Aantoonpeiling',\n  voorspelling: 'Voorspelling (+30 dagen)',\n};\n\n/**\n * Korte vorm voor tabelkolommen waar de ruimte beperkt is.\n *\n * Een reguliere peiling heette in de tabellen eerder `IN`, van inpeiling. Dat\n * beweert meer dan we weten: het onderscheid tussen in- en uitpeiling zit niet\n * in de aanlevering, dus elke peiling die geen aantoonpeiling is kreeg dat\n * label. `Periodiek` zegt precies wat we wel weten.\n */\nexport const PEILSOORT_AFKORTING: Record<PeilsoortEnum, string> = {\n  regulier: 'Periodiek',\n  atp: 'ATP',\n  voorspelling: 'Voorspelling',\n};\n\n/**\n * Kleur waarmee de footprint van een aantoonpeiling op de kaart wordt omlijnd.\n * Gedeeld tussen de kaartlagen, het lagenpaneel en de legenda, zodat de drie\n * niet uit elkaar kunnen lopen.\n */\nexport const ATP_OUTLINE_COLOR = '#d946ef';\n\ntype MetPeilsoort = Pick<SurveyDataset, 'peilsoort'>;\n\nexport function isAtp(ds: MetPeilsoort): boolean {\n  return ds.peilsoort === 'atp';\n}\n\n/** De verwachte bodem +30 dagen: alleen een extra stap op de kaarttijdlijn, nooit in analyses. */\nexport function isVoorspelling(ds: MetPeilsoort): boolean {\n  return ds.peilsoort === 'voorspelling';\n}\n\nexport function isRegulier(ds: MetPeilsoort): boolean {\n  return ds.peilsoort === 'regulier';\n}\n","import { ArrowDown, ArrowUp, ArrowUpDown, Download } from 'lucide-react';\nimport { useMemo, useState } from 'react';\n\nimport { VeranderingTovVorige } from './VakOverviewTable';\n\nimport type { PeilsoortEnum, SurveyDataset } from '@/js/api/types.gen';\n\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/js/components/ui/table';\nimport { ToggleGroup, ToggleGroupItem } from '@/js/components/ui/toggle-group';\nimport { getal } from '@/js/lib/getal-notatie';\nimport {\n  ATP_OUTLINE_COLOR,\n  PEILSOORT_AFKORTING,\n  PEILSOORT_LABELS,\n  isAtp,\n} from '@/js/lib/peilsoort';\nimport { cn } from '@/js/lib/utils';\n\ninterface KuberingTableProps {\n  datasets: SurveyDataset[];\n  vakCodes: string[];\n}\n\ntype ColumnKey =\n  | 'survey_date'\n  | 'baggervak_code'\n  | 'peilsoort'\n  | 'depth_max_in_vak'\n  | 'depth_max_incl_randstrook'\n  | 'surface_ratio_measured'\n  | 'volume_above_ngd_m3'\n  | 'surface_above_ngd_m2'\n  | 'surface_ratio_above_ngd'\n  | 'delta_volume_above_ngd_m3'\n  | 'volume_above_ohd_m3'\n  | 'surface_above_ohd_m2'\n  | 'surface_below_ohd_m2'\n  | 'surface_ratio_below_ohd'\n  | 'delta_volume_above_ohd_m3'\n  | 'vorige_peiling_datum';\n\n/** Hoe een kolom wordt getoond en geëxporteerd. */\ntype ColumnKind = 'date' | 'text' | 'peilsoort' | 'depth' | 'amount' | 'ratio' | 'delta';\n\ninterface ColumnDef {\n  key: ColumnKey;\n  label: string;\n  /**\n   * Kop in de CSV: voluit en met eenheid. De CSV is de rapportage per vak\n   * die het bestek 2026 (art. 32.3.2 lid 2) vraagt, dus hij moet zonder de\n   * tabel ernaast te lezen zijn.\n   */\n  csvLabel: string;\n  kind: ColumnKind;\n  /**\n   * Alleen in de CSV, niet op het scherm. De tabel moet in één scherm passen;\n   * de m² staan al als % in beeld en de verandering staat onder de m³.\n   */\n  alleenCsv?: boolean;\n  /** Verandering sinds de vorige peiling, klein onder dit getal. */\n  verandering?: 'delta_volume_above_ngd_m3' | 'delta_volume_above_ohd_m3';\n}\n\nconst COLUMNS: readonly ColumnDef[] = [\n  { key: 'survey_date', label: 'Datum', csvLabel: 'Peildatum', kind: 'date' },\n  { key: 'baggervak_code', label: 'Vak', csvLabel: 'Baggervak', kind: 'text' },\n  { key: 'peilsoort', label: 'Peilsoort', csvLabel: 'Soort peiling', kind: 'peilsoort' },\n  {\n    key: 'depth_max_in_vak',\n    label: 'Ondiepste, zonder 5 m',\n    csvLabel: 'Ondiepste punt zonder randstrook van 5 m, in m NAP',\n    kind: 'depth',\n  },\n  {\n    key: 'depth_max_incl_randstrook',\n    label: 'met 5 m',\n    csvLabel: 'Ondiepste punt met randstrook van 5 m, in m NAP',\n    kind: 'depth',\n  },\n  {\n    // De percentages hieronder zijn van het hele vak; ongemeten telt daarin\n    // als \"geen overschrijding\". Dit laat zien hoe groot dat deel is.\n    key: 'surface_ratio_measured',\n    label: '% gemeten',\n    csvLabel: 'Gemeten deel van het vak zonder randstrook, in %',\n    kind: 'ratio',\n  },\n  {\n    key: 'volume_above_ngd_m3',\n    label: 'm³ boven NGD',\n    csvLabel: 'Volume boven NGD (minimale diepte), vak zonder randstrook, in m³',\n    kind: 'amount',\n    verandering: 'delta_volume_above_ngd_m3',\n  },\n  {\n    key: 'surface_above_ngd_m2',\n    label: 'm² boven NGD',\n    csvLabel: 'Oppervlak boven NGD (minimale diepte), vak zonder randstrook, in m²',\n    kind: 'amount',\n    alleenCsv: true,\n  },\n  {\n    key: 'surface_ratio_above_ngd',\n    label: '% boven NGD',\n    csvLabel: 'Oppervlak boven NGD (minimale diepte), in % van het hele vak zonder randstrook',\n    kind: 'ratio',\n  },\n  {\n    key: 'delta_volume_above_ngd_m3',\n    label: 'Verandering m³ boven NGD',\n    csvLabel: 'Verandering volume boven NGD sinds de vorige peiling, in m³',\n    kind: 'delta',\n    alleenCsv: true,\n  },\n  {\n    key: 'volume_above_ohd_m3',\n    label: 'm³ boven max.',\n    csvLabel: 'Volume boven de maximale diepte, vak zonder randstrook, in m³',\n    kind: 'amount',\n    verandering: 'delta_volume_above_ohd_m3',\n  },\n  {\n    key: 'surface_above_ohd_m2',\n    label: 'm² boven max.',\n    csvLabel: 'Oppervlak boven de maximale diepte, vak zonder randstrook, in m²',\n    kind: 'amount',\n    alleenCsv: true,\n  },\n  {\n    key: 'surface_below_ohd_m2',\n    label: 'm² dieper dan max.',\n    csvLabel: 'Oppervlak dieper dan de maximale diepte, vak zonder randstrook, in m²',\n    kind: 'amount',\n    alleenCsv: true,\n  },\n  {\n    key: 'surface_ratio_below_ohd',\n    label: '% dieper dan max.',\n    csvLabel: 'Oppervlak dieper dan de maximale diepte, in % van het hele vak zonder randstrook',\n    kind: 'ratio',\n  },\n  {\n    key: 'delta_volume_above_ohd_m3',\n    label: 'Verandering m³ boven max.',\n    csvLabel: 'Verandering volume boven de maximale diepte sinds de vorige peiling, in m³',\n    kind: 'delta',\n    alleenCsv: true,\n  },\n  {\n    key: 'vorige_peiling_datum',\n    label: 'Vergeleken met',\n    csvLabel: 'Datum van de vorige periodieke peiling (vergeleken met)',\n    kind: 'date',\n    alleenCsv: true,\n  },\n] as const;\n\ntype SortDir = 'asc' | 'desc';\n\nfunction alignOf(col: ColumnDef): 'left' | 'right' | 'center' {\n  if (col.kind === 'peilsoort') return 'center';\n  if (col.kind === 'text' || col.kind === 'date') return 'left';\n  return 'right';\n}\n\nfunction formatDate(iso: string | null | undefined): string {\n  if (!iso) return '—';\n  try {\n    return new Date(`${iso}T00:00:00`).toLocaleDateString('nl-NL');\n  } catch {\n    return iso;\n  }\n}\n\n/** De celtekst in de tabel; de peilsoort krijgt een eigen badge. */\nfunction formatCell(row: SurveyDataset, col: ColumnDef): string {\n  const value = row[col.key as keyof SurveyDataset];\n  if (col.kind === 'date') return formatDate(value as string | null);\n  if (value == null || value === '') return '—';\n  if (typeof value !== 'number') return String(value);\n  switch (col.kind) {\n    case 'depth':\n      // Op cm, zoals het overzicht: op dm leest een drempel van −14,96 bij een\n      // NGD van −15,00 als \"−15,0\", terwijl het vak 4 cm te ondiep is.\n      return getal(value, 2);\n    case 'ratio':\n      return `${getal(value * 100, 2)}%`;\n    case 'delta':\n      return getal(value, 0, { plusteken: true });\n    default:\n      return getal(value, 0);\n  }\n}\n\nfunction compareValues(a: SurveyDataset, b: SurveyDataset, key: ColumnKey, dir: SortDir): number {\n  const av = a[key as keyof SurveyDataset];\n  const bv = b[key as keyof SurveyDataset];\n  const mul = dir === 'asc' ? 1 : -1;\n\n  // Nulls sort to the end regardless of direction.\n  if (av == null && bv == null) return 0;\n  if (av == null) return 1;\n  if (bv == null) return -1;\n\n  if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * mul;\n  if (typeof av === 'boolean' && typeof bv === 'boolean') {\n    return (Number(av) - Number(bv)) * mul;\n  }\n  return String(av).localeCompare(String(bv)) * mul;\n}\n\nfunction csvCell(row: SurveyDataset, col: ColumnDef): string {\n  const value = row[col.key as keyof SurveyDataset];\n  if (value == null) return '';\n  if (col.kind === 'peilsoort') return PEILSOORT_LABELS[row.peilsoort];\n  if (typeof value === 'number') {\n    // Dutch locale with comma decimal separator, so Excel-NL opens cleanly.\n    const shown = col.kind === 'ratio' ? value * 100 : value;\n    return shown.toLocaleString('nl-NL', { maximumFractionDigits: 2 });\n  }\n  return String(value).replace(/;/g, ',');\n}\n\nexport function toCsv(rows: SurveyDataset[]): string {\n  const header = COLUMNS.map((c) => c.csvLabel).join(';');\n  const body = rows.map((row) => COLUMNS.map((col) => csvCell(row, col)).join(';')).join('\\n');\n  return `${header}\\n${body}\\n`;\n}\n\nfunction triggerCsvDownload(csv: string, filename: string) {\n  // UTF-8 BOM so Excel-NL opens the file with correct encoding.\n  const blob = new Blob(['﻿', csv], { type: 'text/csv;charset=utf-8' });\n  const url = URL.createObjectURL(blob);\n  const anchor = document.createElement('a');\n  anchor.href = url;\n  anchor.download = filename;\n  document.body.appendChild(anchor);\n  anchor.click();\n  anchor.remove();\n  URL.revokeObjectURL(url);\n}\n\n/** Peilsoort-keuze boven de tabel. `alle` is de stand waarin niets wegvalt. */\ntype PeilsoortFilter = 'alle' | PeilsoortEnum;\n\nconst PEILSOORT_FILTERS: { value: PeilsoortFilter; label: string }[] = [\n  { value: 'alle', label: 'Alle' },\n  { value: 'regulier', label: 'Periodiek' },\n  { value: 'atp', label: 'Aantoonpeiling' },\n];\n\nexport function KuberingTable({ datasets, vakCodes }: KuberingTableProps) {\n  const [sortKey, setSortKey] = useState<ColumnKey>('survey_date');\n  const [sortDir, setSortDir] = useState<SortDir>('desc');\n  const [peilsoortFilter, setPeilsoortFilter] = useState<PeilsoortFilter>('alle');\n\n  const rows = useMemo(() => {\n    const vakSet = new Set(vakCodes);\n    const filtered = datasets.filter(\n      (d) =>\n        vakSet.has(d.baggervak_code) &&\n        d.survey_date &&\n        (peilsoortFilter === 'alle' || d.peilsoort === peilsoortFilter)\n    );\n    return [...filtered].sort((a, b) => compareValues(a, b, sortKey, sortDir));\n  }, [datasets, vakCodes, sortKey, sortDir, peilsoortFilter]);\n\n  // Telling over alle peilsoorten, zodat de gebruiker ziet wat het filter\n  // wegneemt in plaats van alleen wat het overlaat.\n  const aantalPerSoort = useMemo(() => {\n    const vakSet = new Set(vakCodes);\n    const telling: Record<string, number> = {};\n    for (const d of datasets) {\n      if (!vakSet.has(d.baggervak_code) || !d.survey_date) continue;\n      telling[d.peilsoort] = (telling[d.peilsoort] ?? 0) + 1;\n    }\n    return telling;\n  }, [datasets, vakCodes]);\n\n  const toggleSort = (key: ColumnKey) => {\n    if (sortKey === key) {\n      setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));\n    } else {\n      setSortKey(key);\n      setSortDir(key === 'survey_date' ? 'desc' : 'asc');\n    }\n  };\n\n  // De vakkolom alleen bij meer dan één vak: op de Analyse-pagina is het er één.\n  const schermKolommen = COLUMNS.filter(\n    (c) => !c.alleenCsv && (c.key !== 'baggervak_code' || vakCodes.length > 1)\n  );\n\n  const handleExport = () => {\n    const csv = toCsv(rows);\n    const today = new Date().toISOString().slice(0, 10);\n    triggerCsvDownload(csv, `kuberingen-${today}.csv`);\n  };\n\n  const filterBalk = (\n    <div className=\"flex flex-wrap items-center justify-between gap-2\">\n      <div className=\"flex items-center gap-2\">\n        <span className=\"text-xs text-muted-foreground\">Peilsoort:</span>\n        <ToggleGroup\n          type=\"single\"\n          value={peilsoortFilter}\n          onValueChange={(v) => {\n            if (v) setPeilsoortFilter(v as PeilsoortFilter);\n          }}\n          size=\"sm\"\n        >\n          {PEILSOORT_FILTERS.map(({ value, label }) => (\n            <ToggleGroupItem key={value} value={value}>\n              {label}\n              {value !== 'alle' && (\n                <span className=\"ml-1 text-muted-foreground\">({aantalPerSoort[value] ?? 0})</span>\n              )}\n            </ToggleGroupItem>\n          ))}\n        </ToggleGroup>\n      </div>\n      <Button variant=\"outline\" size=\"sm\" onClick={handleExport} disabled={rows.length === 0}>\n        <Download className=\"mr-2 h-4 w-4\" />\n        Export CSV\n      </Button>\n    </div>\n  );\n\n  if (rows.length === 0) {\n    return (\n      <div className=\"space-y-2\">\n        {filterBalk}\n        <div className=\"flex h-24 items-center justify-center rounded-md border text-sm text-muted-foreground\">\n          {peilsoortFilter === 'alle'\n            ? 'Geen peilingen voor de geselecteerde vakken.'\n            : 'Geen peilingen van deze soort voor de geselecteerde vakken.'}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      {filterBalk}\n      <div className=\"rounded-md border\">\n        <Table>\n          <TableHeader>\n            <TableRow>\n              {schermKolommen.map((col) => (\n                <TableHead\n                  key={col.key}\n                  title={col.csvLabel}\n                  className={cn(\n                    'cursor-pointer select-none',\n                    alignOf(col) === 'right' && 'text-right',\n                    alignOf(col) === 'center' && 'text-center'\n                  )}\n                  onClick={() => toggleSort(col.key)}\n                >\n                  <span className=\"inline-flex items-center gap-1\">\n                    {col.label}\n                    <SortIndicator active={sortKey === col.key} dir={sortDir} />\n                  </span>\n                </TableHead>\n              ))}\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {rows.map((row) => (\n              <TableRow key={row.id}>\n                {schermKolommen.map((col) =>\n                  col.kind === 'peilsoort' ? (\n                    <TableCell key={col.key} className=\"text-center\">\n                      {isAtp(row) ? (\n                        <Badge\n                          variant=\"secondary\"\n                          className=\"text-xs\"\n                          style={{ color: ATP_OUTLINE_COLOR }}\n                        >\n                          {PEILSOORT_AFKORTING.atp}\n                        </Badge>\n                      ) : (\n                        <span className=\"text-xs text-muted-foreground\">\n                          {PEILSOORT_AFKORTING.regulier}\n                        </span>\n                      )}\n                    </TableCell>\n                  ) : (\n                    <TableCell\n                      key={col.key}\n                      className={cn(\n                        col.kind === 'text' && 'font-medium',\n                        alignOf(col) === 'right' && 'text-right tabular-nums'\n                      )}\n                    >\n                      {formatCell(row, col)}\n                      {col.verandering && (\n                        <VeranderingTovVorige\n                          delta={row[col.verandering]}\n                          vorigeDatum={row.vorige_peiling_datum}\n                        />\n                      )}\n                    </TableCell>\n                  )\n                )}\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n      </div>\n    </div>\n  );\n}\n\nfunction SortIndicator({ active, dir }: { active: boolean; dir: SortDir }) {\n  if (!active) return <ArrowUpDown className=\"h-3 w-3 text-muted-foreground/60\" />;\n  return dir === 'asc' ? <ArrowUp className=\"h-3 w-3\" /> : <ArrowDown className=\"h-3 w-3\" />;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Client, Options as Options2, TDataShape } from './client';\nimport { client } from './client.gen';\nimport type { ApiAisTracksRetrieveData, ApiAisTracksRetrieveResponses, ApiAisTracksWerkgebiedRetrieveData, ApiAisTracksWerkgebiedRetrieveResponses, ApiBaggerreizenHoeveelhedenRetrieveData, ApiBaggerreizenHoeveelhedenRetrieveResponses, ApiBaggerreizenVerwachtingRetrieveData, ApiBaggerreizenVerwachtingRetrieveResponses, ApiBaggervakkenComputeGeometryCreateData, ApiBaggervakkenComputeGeometryCreateResponses, ApiBaggervakkenCreateData, ApiBaggervakkenCreateResponses, ApiBaggervakkenDestroyData, ApiBaggervakkenDestroyResponses, ApiBaggervakkenListData, ApiBaggervakkenListResponses, ApiBaggervakkenPartialUpdateData, ApiBaggervakkenPartialUpdateResponses, ApiBaggervakkenRecomputeStatsCreateData, ApiBaggervakkenRecomputeStatsCreateResponses, ApiBaggervakkenReferenceCogUrlRetrieveData, ApiBaggervakkenReferenceCogUrlRetrieveResponses, ApiBaggervakkenRetrieveData, ApiBaggervakkenRetrieveResponses, ApiBaggervakkenUpdateData, ApiBaggervakkenUpdateResponses, ApiDatasetsCogUrlRetrieveData, ApiDatasetsCogUrlRetrieveResponses, ApiDatasetsDieptecijfersRetrieveData, ApiDatasetsDieptecijfersRetrieveResponses, ApiDatasetsDieptecontourenRetrieveData, ApiDatasetsDieptecontourenRetrieveResponses, ApiDatasetsGebiedStatistiekCreateData, ApiDatasetsGebiedStatistiekCreateResponses, ApiDatasetsListData, ApiDatasetsListResponses, ApiDatasetsPurgeTrashCreateData, ApiDatasetsPurgeTrashCreateResponses, ApiDatasetsReprocessCreateData, ApiDatasetsReprocessCreateResponses, ApiDatasetsRetrieveData, ApiDatasetsRetrieveResponses, ApiDatasetsSoftDeleteCreateData, ApiDatasetsSoftDeleteCreateResponses, ApiDatasetsSurveyDatesRetrieveData, ApiDatasetsSurveyDatesRetrieveResponses, ApiJobsListData, ApiJobsListResponses, ApiJobsRetrieveData, ApiJobsRetrieveResponses, ApiOmgevingListData, ApiOmgevingListResponses, ApiOverlaysListData, ApiOverlaysListResponses, ApiOverlaysRetrieveData, ApiOverlaysRetrieveResponses, ApiPlaatmigratieCogUrlRetrieveData, ApiPlaatmigratieCogUrlRetrieveResponses, ApiPlaatmigratieFilmRetrieveData, ApiPlaatmigratieFilmRetrieveResponses, ApiPlaatmigratieListData, ApiPlaatmigratieListResponses, ApiReizenUploadsListData, ApiReizenUploadsListResponses, ApiReizenUploadsRetrieveData, ApiReizenUploadsRetrieveResponses, ApiRestRestCheckRetrieveData, ApiRestRestCheckRetrieveResponses, ApiSurroundingBathymetryCogUrlRetrieveData, ApiSurroundingBathymetryCogUrlRetrieveResponses, ApiSurroundingBathymetryFetchCreateData, ApiSurroundingBathymetryFetchCreateResponses, ApiSurroundingBathymetryHillshadeUrlRetrieveData, ApiSurroundingBathymetryHillshadeUrlRetrieveResponses, ApiSurroundingBathymetryListData, ApiSurroundingBathymetryListResponses, ApiSurroundingBathymetryRetrieveData, ApiSurroundingBathymetryRetrieveResponses, ApiSurveysConfirmUploadCreateData, ApiSurveysConfirmUploadCreateResponses, ApiSurveysCreateData, ApiSurveysCreateResponses, ApiSurveysDestroyData, ApiSurveysDestroyResponses, ApiSurveysListData, ApiSurveysListResponses, ApiSurveysPartialUpdateData, ApiSurveysPartialUpdateResponses, ApiSurveysRetrieveData, ApiSurveysRetrieveResponses, ApiSurveysUpdateData, ApiSurveysUpdateResponses, ApiUsersCreateData, ApiUsersCreateResponses, ApiUsersDestroyData, ApiUsersDestroyResponses, ApiUsersListData, ApiUsersListResponses, ApiUsersPartialUpdateData, ApiUsersPartialUpdateResponses, ApiUsersRetrieveData, ApiUsersRetrieveResponses, ApiUsersUpdateData, ApiUsersUpdateResponses } from './types.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {\n    /**\n     * You can provide a client instance returned by `createClient()` instead of\n     * individual options. This might be also useful if you want to implement a\n     * custom client.\n     */\n    client?: Client;\n    /**\n     * You can pass arbitrary values through the `meta` object. This can be\n     * used to access values that aren't defined as part of the SDK function.\n     */\n    meta?: Record<string, unknown>;\n};\n\n/**\n * Vaartracks over een periode, gekleurd naar reisfase.\n */\nexport const apiAisTracksRetrieve = <ThrowOnError extends boolean = false>(options?: Options<ApiAisTracksRetrieveData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiAisTracksRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/ais-tracks/',\n        ...options\n    });\n};\n\n/**\n * Waar het baggerwerk met dit vaklabel lag, en welk werk in deze polygoon viel (WP6).\n */\nexport const apiAisTracksWerkgebiedRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiAisTracksWerkgebiedRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiAisTracksWerkgebiedRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/ais-tracks/werkgebied/',\n        ...options\n    });\n};\n\n/**\n * Gebaggerde hoeveelheden uit de reizendatabase (#119).\n */\nexport const apiBaggerreizenHoeveelhedenRetrieve = <ThrowOnError extends boolean = false>(options?: Options<ApiBaggerreizenHoeveelhedenRetrieveData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiBaggerreizenHoeveelhedenRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggerreizen/hoeveelheden/',\n        ...options\n    });\n};\n\n/**\n * Verwachte baggerinzet voor de komende maand: mediaan van het jaar ervoor (WP6).\n */\nexport const apiBaggerreizenVerwachtingRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiBaggerreizenVerwachtingRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiBaggerreizenVerwachtingRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggerreizen/verwachting/',\n        ...options\n    });\n};\n\nexport const apiBaggervakkenList = <ThrowOnError extends boolean = false>(options?: Options<ApiBaggervakkenListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiBaggervakkenListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/',\n        ...options\n    });\n};\n\nexport const apiBaggervakkenCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiBaggervakkenCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiBaggervakkenCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options?.headers\n        }\n    });\n};\n\nexport const apiBaggervakkenDestroy = <ThrowOnError extends boolean = false>(options: Options<ApiBaggervakkenDestroyData, ThrowOnError>) => {\n    return (options.client ?? client).delete<ApiBaggervakkenDestroyResponses, unknown, ThrowOnError>({\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/{code}/',\n        ...options\n    });\n};\n\nexport const apiBaggervakkenRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiBaggervakkenRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiBaggervakkenRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/{code}/',\n        ...options\n    });\n};\n\nexport const apiBaggervakkenPartialUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiBaggervakkenPartialUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).patch<ApiBaggervakkenPartialUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/{code}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\nexport const apiBaggervakkenUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiBaggervakkenUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).put<ApiBaggervakkenUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/{code}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\n/**\n * SAS URL voor de reference-depth COG van deze baggervak (Sub 7 / #97).\n *\n * Response: ``{\"url\": \"<sas-url>\", \"level\": \"ngd\"}``. 404 als de COG\n * nog niet bestaat — frontend moet daar op failen en terugvallen op het\n * scalar-NGD pad tijdens de rollout.\n */\nexport const apiBaggervakkenReferenceCogUrlRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiBaggervakkenReferenceCogUrlRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiBaggervakkenReferenceCogUrlRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/{code}/reference-cog-url/',\n        ...options\n    });\n};\n\n/**\n * Backfill OBB-velden + cross_section_line voor alle baggervakken.\n *\n * Synchroon (pure Python, ~14 vakken < 1s), idempotent. Wordt ook\n * automatisch aangeroepen aan het begin van :func:`recompute_vak_stats_task`\n * zodat de \"Herbereken statistieken\"-knop self-healing is. Aparte\n * endpoint blijft beschikbaar voor expliciete eenmalige triggers (CLI\n * of curl).\n */\nexport const apiBaggervakkenComputeGeometryCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiBaggervakkenComputeGeometryCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiBaggervakkenComputeGeometryCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/compute-geometry/',\n        ...options\n    });\n};\n\n/**\n * Dispatch a background job that back-fills ``depth_*_in_vak``.\n *\n * Iterates over every historical ``SurveyDataset`` whose per-vak\n * depth statistics are still ``NULL``, clips each COG against the\n * current baggervak polygon, and writes the cached columns. Run\n * this after editing baggervak geometries or after deploying a\n * migration that adds new cached stat columns.\n */\nexport const apiBaggervakkenRecomputeStatsCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiBaggervakkenRecomputeStatsCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiBaggervakkenRecomputeStatsCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/baggervakken/recompute-stats/',\n        ...options\n    });\n};\n\nexport const apiDatasetsList = <ThrowOnError extends boolean = false>(options?: Options<ApiDatasetsListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiDatasetsListResponses, unknown, ThrowOnError>({\n        querySerializer: {\n            array: {\n                explode: false,\n                style: 'form'\n            }\n        },\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/',\n        ...options\n    });\n};\n\nexport const apiDatasetsRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiDatasetsRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/',\n        ...options\n    });\n};\n\n/**\n * Return a time-limited SAS URL for reading the COG.\n */\nexport const apiDatasetsCogUrlRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsCogUrlRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiDatasetsCogUrlRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/cog-url/',\n        ...options\n    });\n};\n\n/**\n * Dieptecijfers voor deze peiling, geknipt op het baggervak (#114).\n *\n * Wiebe vroeg om dieptecijfers ten opzichte van het NGD-model op de\n * kaart. Het rekenen staat in :mod:`surveys.dieptecijfers` en gebeurt op\n * de server, zodat een cijfer op de kaart en een cijfer in de analysetool\n * niet uiteen kunnen lopen: de browser leest bij uitgezoomde weergave de\n * overviews van de COG, en die zijn gemiddeld.\n */\nexport const apiDatasetsDieptecijfersRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsDieptecijfersRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiDatasetsDieptecijfersRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/dieptecijfers/',\n        ...options\n    });\n};\n\n/**\n * Hoogtelijnen voor deze peiling, geknipt op het baggervak (#114).\n *\n * Alleen tegen een peil (NAP, LAT). Een ontwerpdiepte krijgt op de kaart\n * al gekleurde klassen, en een lijn op de klassegrens herhaalt alleen wat\n * de kleur al zegt. Zie :mod:`surveys.dieptecontouren`.\n */\nexport const apiDatasetsDieptecontourenRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsDieptecontourenRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiDatasetsDieptecontourenRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/dieptecontouren/',\n        ...options\n    });\n};\n\n/**\n * Re-trigger processing for a dataset.\n */\nexport const apiDatasetsReprocessCreate = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsReprocessCreateData, ThrowOnError>) => {\n    return (options.client ?? client).post<ApiDatasetsReprocessCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/reprocess/',\n        ...options\n    });\n};\n\n/**\n * Soft-delete a dataset by setting its status to DELETED.\n */\nexport const apiDatasetsSoftDeleteCreate = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsSoftDeleteCreateData, ThrowOnError>) => {\n    return (options.client ?? client).post<ApiDatasetsSoftDeleteCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/{id}/soft-delete/',\n        ...options\n    });\n};\n\n/**\n * Diepte- en kuberingsstatistiek binnen een zelfgetekend gebied (#115).\n *\n * Binnen de Eemsgeul wordt op de ondiepste waarde gestuurd; een gebied\n * aanwijzen en er meteen de gemiddelde en de ondiepste diepte van zien is\n * de vraag van Wiebe uit de sessie van 16-06-2026.\n *\n * Het rekenwerk staat in :mod:`surveys.gebied` en loopt door dezelfde\n * functies als de vak-brede cijfers, zodat gebied en vak niet uiteen\n * kunnen lopen.\n */\nexport const apiDatasetsGebiedStatistiekCreate = <ThrowOnError extends boolean = false>(options: Options<ApiDatasetsGebiedStatistiekCreateData, ThrowOnError>) => {\n    return (options.client ?? client).post<ApiDatasetsGebiedStatistiekCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/gebied-statistiek/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\n/**\n * Permanently delete all trashed datasets and their blobs.\n */\nexport const apiDatasetsPurgeTrashCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiDatasetsPurgeTrashCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiDatasetsPurgeTrashCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/purge-trash/',\n        ...options\n    });\n};\n\n/**\n * Unieke maand-startdata van alle ready datasets, oplopend gesorteerd.\n *\n * Lichtgewicht alternatief voor het ophalen van álle datasets alleen om\n * de tijdlijn-stops af te leiden (was: lijst-call met limit=1000 incl.\n * zware JSON-velden).\n */\nexport const apiDatasetsSurveyDatesRetrieve = <ThrowOnError extends boolean = false>(options?: Options<ApiDatasetsSurveyDatesRetrieveData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiDatasetsSurveyDatesRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/datasets/survey-dates/',\n        ...options\n    });\n};\n\nexport const apiJobsList = <ThrowOnError extends boolean = false>(options?: Options<ApiJobsListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiJobsListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/jobs/',\n        ...options\n    });\n};\n\nexport const apiJobsRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiJobsRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiJobsRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/jobs/{id}/',\n        ...options\n    });\n};\n\n/**\n * De omgevingskaarten voor de 2D-kaart (Omgeving actueel), ~25 rijen, geen paginering.\n *\n * Een voorspellingskaart komt alleen mee als zijn datum een huidige READY\n * VOORSPELLING-datum is; een verlopen voorspelling blijft weg.\n */\nexport const apiOmgevingList = <ThrowOnError extends boolean = false>(options?: Options<ApiOmgevingListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiOmgevingListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/omgeving/',\n        ...options\n    });\n};\n\nexport const apiOverlaysList = <ThrowOnError extends boolean = false>(options?: Options<ApiOverlaysListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiOverlaysListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/overlays/',\n        ...options\n    });\n};\n\nexport const apiOverlaysRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiOverlaysRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiOverlaysRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/overlays/{id}/',\n        ...options\n    });\n};\n\nexport const apiPlaatmigratieList = <ThrowOnError extends boolean = false>(options?: Options<ApiPlaatmigratieListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiPlaatmigratieListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/plaatmigratie/',\n        ...options\n    });\n};\n\nexport const apiPlaatmigratieCogUrlRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiPlaatmigratieCogUrlRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiPlaatmigratieCogUrlRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/plaatmigratie/{jaar}/cog-url/',\n        ...options\n    });\n};\n\nexport const apiPlaatmigratieFilmRetrieve = <ThrowOnError extends boolean = false>(options?: Options<ApiPlaatmigratieFilmRetrieveData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiPlaatmigratieFilmRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/plaatmigratie/film/',\n        ...options\n    });\n};\n\n/**\n * De aanleveringen zelf, met hun importverslag.\n */\nexport const apiReizenUploadsList = <ThrowOnError extends boolean = false>(options?: Options<ApiReizenUploadsListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiReizenUploadsListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/reizen-uploads/',\n        ...options\n    });\n};\n\n/**\n * De aanleveringen zelf, met hun importverslag.\n */\nexport const apiReizenUploadsRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiReizenUploadsRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiReizenUploadsRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/reizen-uploads/{id}/',\n        ...options\n    });\n};\n\n/**\n * Check REST API\n *\n * This endpoint checks if the REST API is working.\n */\nexport const apiRestRestCheckRetrieve = <ThrowOnError extends boolean = false>(options?: Options<ApiRestRestCheckRetrieveData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiRestRestCheckRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/rest/rest-check/',\n        ...options\n    });\n};\n\nexport const apiSurroundingBathymetryList = <ThrowOnError extends boolean = false>(options?: Options<ApiSurroundingBathymetryListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiSurroundingBathymetryListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surrounding-bathymetry/',\n        ...options\n    });\n};\n\nexport const apiSurroundingBathymetryRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiSurroundingBathymetryRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiSurroundingBathymetryRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surrounding-bathymetry/{year}/',\n        ...options\n    });\n};\n\n/**\n * Return a time-limited SAS URL for reading the surrounding bathymetry COG.\n */\nexport const apiSurroundingBathymetryCogUrlRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiSurroundingBathymetryCogUrlRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiSurroundingBathymetryCogUrlRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surrounding-bathymetry/{year}/cog-url/',\n        ...options\n    });\n};\n\n/**\n * Return a time-limited SAS URL for the hillshade COG.\n */\nexport const apiSurroundingBathymetryHillshadeUrlRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiSurroundingBathymetryHillshadeUrlRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiSurroundingBathymetryHillshadeUrlRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surrounding-bathymetry/{year}/hillshade-url/',\n        ...options\n    });\n};\n\n/**\n * Download RWS data for given years, convert to COG, upload to Azure.\n *\n * POST /api/surrounding-bathymetry/fetch/?years=2025,2024&hillshade=true\n */\nexport const apiSurroundingBathymetryFetchCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiSurroundingBathymetryFetchCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiSurroundingBathymetryFetchCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surrounding-bathymetry/fetch/',\n        ...options\n    });\n};\n\nexport const apiSurveysList = <ThrowOnError extends boolean = false>(options?: Options<ApiSurveysListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiSurveysListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/',\n        ...options\n    });\n};\n\nexport const apiSurveysCreate = <ThrowOnError extends boolean = false>(options?: Options<ApiSurveysCreateData, ThrowOnError>) => {\n    return (options?.client ?? client).post<ApiSurveysCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options?.headers\n        }\n    });\n};\n\nexport const apiSurveysDestroy = <ThrowOnError extends boolean = false>(options: Options<ApiSurveysDestroyData, ThrowOnError>) => {\n    return (options.client ?? client).delete<ApiSurveysDestroyResponses, unknown, ThrowOnError>({\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/{id}/',\n        ...options\n    });\n};\n\nexport const apiSurveysRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiSurveysRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiSurveysRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/{id}/',\n        ...options\n    });\n};\n\nexport const apiSurveysPartialUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiSurveysPartialUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).patch<ApiSurveysPartialUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/{id}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\nexport const apiSurveysUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiSurveysUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).put<ApiSurveysUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/{id}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\n/**\n * Confirm that the blob upload is complete and trigger processing.\n */\nexport const apiSurveysConfirmUploadCreate = <ThrowOnError extends boolean = false>(options: Options<ApiSurveysConfirmUploadCreateData, ThrowOnError>) => {\n    return (options.client ?? client).post<ApiSurveysConfirmUploadCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/surveys/{id}/confirm-upload/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\nexport const apiUsersList = <ThrowOnError extends boolean = false>(options?: Options<ApiUsersListData, ThrowOnError>) => {\n    return (options?.client ?? client).get<ApiUsersListResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/',\n        ...options\n    });\n};\n\nexport const apiUsersCreate = <ThrowOnError extends boolean = false>(options: Options<ApiUsersCreateData, ThrowOnError>) => {\n    return (options.client ?? client).post<ApiUsersCreateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\nexport const apiUsersDestroy = <ThrowOnError extends boolean = false>(options: Options<ApiUsersDestroyData, ThrowOnError>) => {\n    return (options.client ?? client).delete<ApiUsersDestroyResponses, unknown, ThrowOnError>({\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/{id}/',\n        ...options\n    });\n};\n\nexport const apiUsersRetrieve = <ThrowOnError extends boolean = false>(options: Options<ApiUsersRetrieveData, ThrowOnError>) => {\n    return (options.client ?? client).get<ApiUsersRetrieveResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/{id}/',\n        ...options\n    });\n};\n\nexport const apiUsersPartialUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiUsersPartialUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).patch<ApiUsersPartialUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/{id}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n\nexport const apiUsersUpdate = <ThrowOnError extends boolean = false>(options: Options<ApiUsersUpdateData, ThrowOnError>) => {\n    return (options.client ?? client).put<ApiUsersUpdateResponses, unknown, ThrowOnError>({\n        responseType: 'json',\n        security: [\n            {\n                in: 'cookie',\n                name: 'sessionid',\n                type: 'apiKey'\n            }\n        ],\n        url: '/api/users/{id}/',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options.headers\n        }\n    });\n};\n","import { useState } from 'react';\nimport {\n  Bar,\n  BarChart,\n  CartesianGrid,\n  ErrorBar,\n  ResponsiveContainer,\n  Tooltip,\n  XAxis,\n  YAxis,\n} from 'recharts';\n\nimport type { BaggerverwachtingResponse, HoeveelheidPunt, PeriodeEnum } from '@/js/api/types.gen';\n\nimport { ToggleGroup, ToggleGroupItem } from '@/js/components/ui/toggle-group';\nimport { useBaggerhoeveelheden } from '@/js/hooks/use-baggerhoeveelheden';\nimport { useBaggerverwachting } from '@/js/hooks/use-baggerverwachting';\n\nconst PERIODES: { value: PeriodeEnum; label: string }[] = [\n  { value: 'dag', label: 'Dag' },\n  { value: 'week', label: 'Week' },\n  { value: 'maand', label: 'Maand' },\n];\n\nconst KLEUR = '#0f766e';\n\n/** Wat er per staaf op de as komt te staan. */\nfunction periodeLabel(start: string, periode: PeriodeEnum): string {\n  const datum = new Date(`${start}T00:00:00`);\n  if (periode === 'maand') {\n    return datum.toLocaleDateString('nl-NL', { month: 'short', year: '2-digit' });\n  }\n  const dagMaand = datum.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });\n  return periode === 'week' ? `wk ${dagMaand}` : dagMaand;\n}\n\ntype Staaf = {\n  label: string;\n  volume?: number;\n  reizen?: number;\n  /** Alleen bij de verwachte maand: de mediaan van het jaar ervoor. */\n  verwacht?: number;\n  /** Afstand van de mediaan tot het 10e en 90e percentiel, voor de foutbalk. */\n  band?: [number, number];\n};\n\n/**\n * De gemeten maanden plus, achteraan, de verwachte maand als eigen staaf.\n *\n * Eigen staaf en niet in de staaf van die maand: de laatste aanlevering\n * beslaat de verwachte maand vaak al half, en dan staan het halve gemeten\n * getal en de verwachting naast elkaar in plaats van door elkaar.\n */\nexport function staven(\n  punten: HoeveelheidPunt[],\n  periode: PeriodeEnum,\n  verwachting: BaggerverwachtingResponse | null\n): Staaf[] {\n  const uit: Staaf[] = punten.map((punt) => ({\n    label: periodeLabel(punt.start, periode),\n    volume: punt.volume_m3,\n    reizen: punt.reizen,\n  }));\n  const v = verwachting?.verwachting;\n  if (periode === 'maand' && v) {\n    uit.push({\n      label: `${periodeLabel(v.maand, 'maand')} verw.`,\n      verwacht: v.mediaan_m3,\n      band: [v.mediaan_m3 - v.laag_m3, v.hoog_m3 - v.mediaan_m3],\n    });\n  }\n  return uit;\n}\n\n/** Afgerond op een maat die past bij een verwachting: geen valse precisie. */\nexport function ongeveer(m3: number): string {\n  const stap = m3 >= 10_000 ? 1000 : m3 >= 1000 ? 100 : 10;\n  return (Math.round(m3 / stap) * stap).toLocaleString('nl-NL');\n}\n\n/**\n * Gebaggerde hoeveelheid per dag, week of maand uit de reizendatabase (#119).\n *\n * Staat bewust direct onder de volumeontwikkeling van hetzelfde vak: samen\n * beantwoorden ze de vraag of een dalend volume boven NGD door baggeren komt\n * of vanzelf.\n */\nexport function VakBaggerhoeveelheden({ code }: { code: string }) {\n  const [periode, setPeriode] = useState<PeriodeEnum>('maand');\n  const { data, bezig, fout } = useBaggerhoeveelheden(code, periode);\n  const { data: verwachting } = useBaggerverwachting(code);\n\n  const punten = data?.punten ?? [];\n  const chartData = staven(punten, periode, verwachting);\n\n  return (\n    <div className=\"space-y-2\">\n      <div className=\"flex items-center gap-2\">\n        <span className=\"text-xs text-muted-foreground\">Per:</span>\n        <ToggleGroup\n          type=\"single\"\n          value={periode}\n          onValueChange={(v) => {\n            if (v) setPeriode(v as PeriodeEnum);\n          }}\n          size=\"sm\"\n        >\n          {PERIODES.map((p) => (\n            <ToggleGroupItem key={p.value} value={p.value}>\n              {p.label}\n            </ToggleGroupItem>\n          ))}\n        </ToggleGroup>\n      </div>\n\n      {fout ? (\n        <Melding>De baggerhoeveelheden zijn niet op te halen.</Melding>\n      ) : bezig && punten.length === 0 ? (\n        <Melding>Bezig met ophalen…</Melding>\n      ) : punten.length === 0 ? (\n        <Melding>\n          Geen baggeractiviteit bekend voor dit vak. De reizendatabase wordt per aanlevering\n          ingelezen; zolang die er niet is, blijft deze grafiek leeg.\n        </Melding>\n      ) : (\n        <>\n          <div className=\"h-56\">\n            <ResponsiveContainer width=\"100%\" height=\"100%\">\n              <BarChart data={chartData}>\n                <CartesianGrid strokeDasharray=\"3 3\" />\n                <XAxis dataKey=\"label\" fontSize={12} />\n                <YAxis\n                  fontSize={12}\n                  label={{\n                    value: 'm³',\n                    angle: -90,\n                    position: 'insideLeft',\n                    style: { fontSize: 12 },\n                  }}\n                />\n                <Tooltip\n                  formatter={(v, naam) =>\n                    naam === 'volume'\n                      ? `${Math.round(Number(v)).toLocaleString('nl-NL')} m³`\n                      : naam === 'verwacht'\n                        ? [`ongeveer ${ongeveer(Number(v))} m³`, 'verwacht']\n                        : String(v)\n                  }\n                />\n                {/* Gestapeld zodat de staven hun volle breedte houden: een staaf\n                    heeft óf een volume óf een verwachting, nooit allebei. */}\n                <Bar dataKey=\"volume\" stackId=\"m3\" fill={KLEUR} isAnimationActive={false} />\n                <Bar\n                  dataKey=\"verwacht\"\n                  stackId=\"m3\"\n                  fill={KLEUR}\n                  fillOpacity={0.15}\n                  stroke={KLEUR}\n                  strokeDasharray=\"4 2\"\n                  isAnimationActive={false}\n                >\n                  <ErrorBar dataKey=\"band\" width={6} stroke={KLEUR} direction=\"y\" />\n                </Bar>\n              </BarChart>\n            </ResponsiveContainer>\n          </div>\n          <Toelichting\n            label={data?.grootheid_label ?? ''}\n            totaal={data?.totaal_m3 ?? 0}\n            reizen={data?.reizen ?? 0}\n            zonderVolume={data?.zonder_volume ?? 0}\n          />\n          {periode === 'maand' && verwachting && <Verwachtingsregel data={verwachting} />}\n        </>\n      )}\n    </div>\n  );\n}\n\nfunction Melding({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex h-48 items-center justify-center px-6 text-center text-sm text-muted-foreground\">\n      {children}\n    </div>\n  );\n}\n\n/**\n * Zegt welk volume er geteld is en wat er buiten de grafiek valt. Zonder deze\n * regel is niet te zien dat \"in-situ\" en \"beun\" verschillende grootheden zijn,\n * en al helemaal niet dat er reizen zijn die niet meetellen.\n */\nfunction Toelichting({\n  label,\n  totaal,\n  reizen,\n  zonderVolume,\n}: {\n  label: string;\n  totaal: number;\n  reizen: number;\n  zonderVolume: number;\n}) {\n  return (\n    <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n      <span>\n        Geteld: {label}. {Math.round(totaal).toLocaleString('nl-NL')} m³ over {reizen}{' '}\n        {reizen === 1 ? 'reis' : 'reizen'}.\n      </span>\n      {zonderVolume > 0 && (\n        <span>\n          {zonderVolume} {zonderVolume === 1 ? 'reis heeft' : 'reizen hebben'} geen {label} en\n          {zonderVolume === 1 ? ' telt' : ' tellen'} niet mee.\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction maandNaam(iso: string): string {\n  return new Date(`${iso}T00:00:00`).toLocaleDateString('nl-NL', {\n    month: 'long',\n    year: 'numeric',\n  });\n}\n\n/** De verwachting voor de volgende maand in woorden. */\nexport function Verwachtingsregel({ data }: { data: BaggerverwachtingResponse }) {\n  const v = data.verwachting;\n  if (!v) {\n    return data.reden ? (\n      <p className=\"text-xs text-muted-foreground\">Geen verwachting: {data.reden}</p>\n    ) : null;\n  }\n  return (\n    <div className=\"space-y-1 rounded-md border border-dashed p-2 text-xs text-muted-foreground\">\n      <p>\n        <span className=\"font-medium text-foreground\">\n          Verwachting {maandNaam(v.maand)}: ongeveer {ongeveer(v.mediaan_m3)} m³\n        </span>\n        , meestal tussen {ongeveer(v.laag_m3)} en {ongeveer(v.hoog_m3)} m³. Dat is de mediaan en de\n        spreiding (10–90 %) van de twaalf maanden ervoor\n        {data.data_tot\n          ? `, met reizen tot en met ${new Date(`${data.data_tot}T00:00:00`).toLocaleDateString('nl-NL')}`\n          : ''}\n        .\n      </p>\n    </div>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { HoeveelhedenResponse, PeriodeEnum } from '@/js/api/types.gen';\n\nimport { apiBaggerreizenHoeveelhedenRetrieve } from '@/js/api/sdk.gen';\n\n/**\n * Gebaggerde hoeveelheden per periode voor één vak (#119).\n *\n * De reeks is klein (hooguit een paar honderd staven) en verandert alleen\n * wanneer er een nieuwe aanlevering binnenkomt, dus hij wordt per\n * (vak, periode) opgehaald zonder verdere cache. Een leeg antwoord is geen\n * fout: bij de meeste vakken zijn er simpelweg geen reizen aangeleverd.\n */\nexport function useBaggerhoeveelheden(code: string | null, periode: PeriodeEnum) {\n  const [data, setData] = useState<HoeveelhedenResponse | null>(null);\n  const [bezig, setBezig] = useState(false);\n  const [fout, setFout] = useState(false);\n\n  useEffect(() => {\n    if (!code) {\n      setData(null);\n      setBezig(false);\n      setFout(false);\n      return;\n    }\n    let afgebroken = false;\n    setBezig(true);\n    setFout(false);\n\n    apiBaggerreizenHoeveelhedenRetrieve({ query: { vak: code, periode } })\n      .then(({ data: antwoord }) => {\n        if (afgebroken) return;\n        setData(antwoord ?? null);\n      })\n      .catch(() => {\n        if (afgebroken) return;\n        setData(null);\n        setFout(true);\n      })\n      .finally(() => {\n        if (!afgebroken) setBezig(false);\n      });\n\n    return () => {\n      afgebroken = true;\n    };\n  }, [code, periode]);\n\n  return { data, bezig, fout };\n}\n","import { useEffect, useState } from 'react';\n\nimport type { BaggerverwachtingResponse } from '@/js/api/types.gen';\n\nimport { apiBaggerreizenVerwachtingRetrieve } from '@/js/api/sdk.gen';\n\n/**\n * Verwachte baggerinzet voor de komende maand in één vak (WP6 versie 1).\n *\n * Zelfde opzet als {@link useBaggerhoeveelheden}: klein antwoord, verandert\n * alleen met een nieuwe aanlevering, dus geen cache. Een fout hier mag de\n * grafiek met de gemeten hoeveelheden niet raken; de aanroeper toont dan\n * gewoon geen verwachting.\n */\nexport function useBaggerverwachting(code: string | null) {\n  const [data, setData] = useState<BaggerverwachtingResponse | null>(null);\n  const [fout, setFout] = useState(false);\n\n  useEffect(() => {\n    setData(null);\n    setFout(false);\n    if (!code) return;\n    let afgebroken = false;\n\n    apiBaggerreizenVerwachtingRetrieve({ query: { vak: code } })\n      .then(({ data: antwoord }) => {\n        if (!afgebroken) setData(antwoord ?? null);\n      })\n      .catch(() => {\n        if (!afgebroken) setFout(true);\n      });\n\n    return () => {\n      afgebroken = true;\n    };\n  }, [code]);\n\n  return { data, fout };\n}\n","/**\n * Panamax-tanker cross-section dimensies (meters).\n * Geldig voor de Panamax-klasse die de Eemsgeul kan passeren — niet de\n * post-Panamax of New-Panamax. Zie meeting-notitie 2026-05-22 (Jan).\n */\nexport const PANAMAX_BEAM = 32; // breedte aan waterlijn\nexport const PANAMAX_DRAFT = 12; // diepgang\nexport const PANAMAX_FREEBOARD = 8; // hoogte boven waterlijn\nexport const PANAMAX_BOTTOM = 22; // platte bodembreedte (smaller dan beam)\n\nexport interface PanamaxHullProps {\n  /** Pixel-x voor een waarde in data-coords (m langs cross-section). */\n  xScale: (x: number) => number;\n  /** Pixel-y voor een waarde in data-coords (m NAP). */\n  yScale: (y: number) => number;\n  /** Data-x waar de romp gecentreerd wordt. */\n  centerX: number;\n  /** Optionele kleur-override. Default: donkergrijs met opacity. */\n  fill?: string;\n  /** Optioneel label tonen? Default true. */\n  showLabel?: boolean;\n}\n\n/**\n * Render-vrije, herbruikbare SVG-romp.\n *\n * Geen Recharts-koppeling: caller geeft `xScale`/`yScale`-callbacks die\n * data-coords naar pixel-coords mappen. Hierdoor werkt het zowel binnen\n * `<Customized>` in een Recharts-chart als binnen een plain `<svg>`.\n */\nexport function PanamaxHull({\n  xScale,\n  yScale,\n  centerX,\n  fill = '#4a4a4a',\n  showLabel = true,\n}: PanamaxHullProps) {\n  const halfBeam = PANAMAX_BEAM / 2;\n  const halfBottom = PANAMAX_BOTTOM / 2;\n  const deck = PANAMAX_FREEBOARD; // top of ship (m NAP)\n  const waterline = 0;\n  const keel = -PANAMAX_DRAFT;\n\n  const px = xScale;\n  const py = yScale;\n\n  const d = [\n    `M ${px(centerX - halfBeam)} ${py(deck)}`,\n    `L ${px(centerX - halfBeam)} ${py(waterline)}`,\n    `Q ${px(centerX - halfBeam)} ${py(keel)}, ${px(centerX - halfBottom)} ${py(keel)}`,\n    `L ${px(centerX + halfBottom)} ${py(keel)}`,\n    `Q ${px(centerX + halfBeam)} ${py(keel)}, ${px(centerX + halfBeam)} ${py(waterline)}`,\n    `L ${px(centerX + halfBeam)} ${py(deck)}`,\n    'Z',\n  ].join(' ');\n\n  return (\n    <g>\n      <path d={d} fill={fill} fillOpacity={0.5} stroke=\"#333\" strokeWidth={1.5} />\n      <line\n        x1={px(centerX - halfBeam - 2)}\n        x2={px(centerX + halfBeam + 2)}\n        y1={py(waterline)}\n        y2={py(waterline)}\n        stroke=\"#3b82f6\"\n        strokeWidth={1}\n        strokeDasharray=\"3 2\"\n      />\n      {showLabel && (\n        <text\n          x={px(centerX)}\n          y={py(deck) - 4}\n          textAnchor=\"middle\"\n          fontSize={12}\n          fill=\"#555\"\n        >\n          Panamax ({PANAMAX_BEAM}m × {PANAMAX_DRAFT}m)\n        </text>\n      )}\n    </g>\n  );\n}\n","// frontend/js/lib/reference-levels.ts\n\n/** Colours matching CrossSectionDrawer (NGD red, IVD orange, OHD purple). */\nexport const REF_LEVEL_COLORS = {\n  ngd: '#ef4444',\n  ivd: '#f97316',\n  ohd: '#8b5cf6',\n} as const;\n\n/**\n * OHD heet in de interface \"max.\": het is de maximale waterbodemdiepte uit het\n * bestek 2026. IVD komt daar niet meer in voor; de Analyse en de kaart tonen\n * hem niet meer. De sleutel staat er nog omdat het type van de grafieken hem\n * kent (`DepthTimeChart`).\n */\nexport const REF_LEVEL_LABELS = {\n  lat: 'LAT',\n  ngd: 'NGD',\n  ivd: 'IVD',\n  ohd: 'max.',\n} as const;\n\n/** NAP water-surface reference line. */\nexport const NAP_COLOR = '#3b82f6';\n","import { PanamaxHull, PANAMAX_FREEBOARD } from '@/js/components/PanamaxHull';\nimport { REF_LEVEL_COLORS, REF_LEVEL_LABELS } from '@/js/lib/reference-levels';\n\nexport interface CrossSectionSample {\n  distance_m: number;\n  depth_m: number | null;\n}\n\nexport interface VakCrossSectionProps {\n  samples: ReadonlyArray<CrossSectionSample>;\n  referenceLevels: {\n    ngd: number | null;\n    ohd: number | null;\n  };\n  width: number;\n  height: number;\n}\n\n/**\n * Statische dwarsdoorsnede met water-vlak, bodemprofiel, lijnen voor NGD en\n * de maximale diepte (OHD)\n * en een Panamax-romp in het midden. Geen Recharts; directe SVG zodat de\n * Panamax-romp dezelfde scale-mapping gebruikt als het bodemprofiel.\n */\nexport function VakCrossSection({ samples, referenceLevels, width, height }: VakCrossSectionProps) {\n  if (samples.length === 0) {\n    return (\n      <div className=\"flex h-full items-center justify-center text-xs text-muted-foreground\">\n        Geen dwarsprofiel-data beschikbaar.\n      </div>\n    );\n  }\n\n  const margin = { top: 16, right: 16, bottom: 24, left: 32 };\n  const innerW = width - margin.left - margin.right;\n  const innerH = height - margin.top - margin.bottom;\n\n  const xs = samples.map((s) => s.distance_m);\n  const xMin = Math.min(...xs);\n  const xMax = Math.max(...xs);\n\n  const depths = samples.map((s) => s.depth_m).filter((d): d is number => d != null);\n  const refValues = [referenceLevels.ngd, referenceLevels.ohd].filter(\n    (v): v is number => v != null\n  );\n  const yMin = depths.length > 0 ? Math.min(...depths) - 1 : -20;\n  const yMax = PANAMAX_FREEBOARD + 2;\n  const yMinAll = Math.min(yMin, ...refValues, -1);\n\n  const xScale = (x: number) => margin.left + ((x - xMin) / Math.max(1e-6, xMax - xMin)) * innerW;\n  const yScale = (y: number) =>\n    margin.top + innerH - ((y - yMinAll) / Math.max(1e-6, yMax - yMinAll)) * innerH;\n\n  // Bodem-pad: top-rand op depth, sluiten naar onderkant.\n  const seabedPath =\n    samples\n      .map((s, i) => {\n        if (s.depth_m == null) return null;\n        const sx = xScale(s.distance_m);\n        const sy = yScale(s.depth_m);\n        return `${i === 0 ? 'M' : 'L'}${sx.toFixed(2)} ${sy.toFixed(2)}`;\n      })\n      .filter(Boolean)\n      .join(' ') +\n    ` L${xScale(xMax).toFixed(2)} ${yScale(yMinAll).toFixed(2)}` +\n    ` L${xScale(xMin).toFixed(2)} ${yScale(yMinAll).toFixed(2)} Z`;\n\n  const cx = (xMin + xMax) / 2;\n\n  return (\n    <svg\n      className=\"h-auto w-full\"\n      viewBox={`0 0 ${width} ${height}`}\n      role=\"img\"\n      aria-label=\"Dwarsdoorsnede met Panamax-referentie\"\n    >\n      <defs>\n        <linearGradient id=\"vakCrossSectionWater\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor=\"#60a5fa\" stopOpacity={0.25} />\n          <stop offset=\"100%\" stopColor=\"#1e40af\" stopOpacity={0.4} />\n        </linearGradient>\n        <linearGradient id=\"vakCrossSectionSand\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor=\"#C4A46C\" stopOpacity={0.85} />\n          <stop offset=\"100%\" stopColor=\"#8B7355\" stopOpacity={0.95} />\n        </linearGradient>\n      </defs>\n      {/* Water-vlak (van top tot NAP=0) */}\n      <rect\n        x={margin.left}\n        y={yScale(yMax)}\n        width={innerW}\n        height={yScale(0) - yScale(yMax)}\n        fill=\"url(#vakCrossSectionWater)\"\n      />\n      {/* Bodem */}\n      <path d={seabedPath} fill=\"url(#vakCrossSectionSand)\" stroke=\"#6B5B3E\" strokeWidth={1.5} />\n      {/* Reference-lijnen */}\n      {(['ngd', 'ohd'] as const).map((key) => {\n        const level = referenceLevels[key];\n        if (level == null) return null;\n        return (\n          <g key={key}>\n            <line\n              data-ref-line={key}\n              x1={margin.left}\n              x2={margin.left + innerW}\n              y1={yScale(level)}\n              y2={yScale(level)}\n              stroke={REF_LEVEL_COLORS[key]}\n              strokeWidth={1.5}\n              strokeDasharray=\"5 5\"\n            />\n          </g>\n        );\n      })}\n      {/* Eén regel voor de vlakken: ze liggen vaak binnen een halve meter\n          van elkaar, en losse labels bij de lijnen vielen over elkaar heen. */}\n      <text x={margin.left + innerW} y={height - 4} textAnchor=\"end\" fontSize={12}>\n        {(['ngd', 'ohd'] as const).map((key) => {\n          const level = referenceLevels[key];\n          if (level == null) return null;\n          return (\n            <tspan key={key} dx={6} fill={REF_LEVEL_COLORS[key]}>\n              {REF_LEVEL_LABELS[key]} {level.toFixed(1)} m\n            </tspan>\n          );\n        })}\n      </text>\n      {/* NAP-lijn */}\n      <line\n        x1={margin.left}\n        x2={margin.left + innerW}\n        y1={yScale(0)}\n        y2={yScale(0)}\n        stroke=\"#3b82f6\"\n        strokeWidth={1}\n        strokeDasharray=\"6 3\"\n      />\n      {/* Panamax-romp centraal */}\n      <PanamaxHull xScale={xScale} yScale={yScale} centerX={cx} />\n    </svg>\n  );\n}\n","/** Local benchmark only. Spatial resolution experiments never change originals\n * or silently reduce the quality of a normal/production map. Reload to change. */\nexport function kaartExperiment(name: string, allowed: number[], fallback: number): number {\n  try {\n    if (\n      !['localhost', '127.0.0.1'].includes(window.location.hostname) ||\n      localStorage.getItem('kaartBenchmark') !== '1'\n    )\n      return fallback;\n    const value = Number(localStorage.getItem(name));\n    return allowed.includes(value) ? value : fallback;\n  } catch {\n    return fallback;\n  }\n}\n\n// Keep 256 actual pixels per XYZ tile, but request a coarser XYZ zoom level.\n// 2 = half the resolution in each direction; 4 = one quarter.\nexport const rasterResolutionFactor = kaartExperiment('kaartRasterFactor', [1, 2, 4], 1);\n","/**\n * Opt-in opname voor datumwissels, kaartbewegingen en achtergrondladen.\n *\n * Staat standaard uit en kost dan niets. Aanzetten in de console met\n * `localStorage.tijdlijnMeting = '1'` en de pagina herladen; daarna:\n *\n *   __tijdlijn.reset()      // vlak voor je op een pijltjestoets drukt\n *   __tijdlijn.rapport()    // zodra de kaart tot rust is gekomen\n *\n * `__tijdlijn.download()` bewaart een begrensde JSON-opname. Zie\n * doc/tijdlijn-performance.md voor het meetprotocol en interpretatie.\n */\n\nimport { kaartExperiment } from './kaart-experiment';\n\ntype Teller = { aantal: number; ms: number; maxMs: number; fouten: number };\ntype Details = Record<string, string | number | boolean | null>;\ntype Gebeurtenis = {\n  naam: string;\n  startMs: number;\n  duurMs?: number;\n  status?: string;\n  details?: Details;\n};\nconst MAX_EVENTS = kaartExperiment('kaartMeetEvents', [2000, 20000], 2000);\nconst gebeurtenissen: Gebeurtenis[] = [];\nconst interacties: Gebeurtenis[] = [];\nconst onderweg = new Set<Gebeurtenis>();\nlet generatie = 0;\nlet weggelaten = 0;\n\nconst tellers = new Map<string, Teller>();\nlet startTijd = 0;\n\nfunction aan(): boolean {\n  try {\n    return typeof localStorage !== 'undefined' && localStorage.getItem('tijdlijnMeting') === '1';\n  } catch {\n    return false;\n  }\n}\n\nexport const tijdlijnMetingAan = aan();\nconst ingeschakeld = tijdlijnMetingAan;\n\nfunction bewaar(event: Gebeurtenis): void {\n  // Tile/network spans must not evict the beginning of the user scenario.\n  const buffer = /^(kaart\\.|tijdlijn\\.|maand\\.|api\\.)/.test(event.naam)\n    ? interacties\n    : gebeurtenissen;\n  if (buffer.length === MAX_EVENTS) {\n    buffer.shift();\n    weggelaten++;\n  }\n  buffer.push(event);\n}\n\n/** Alleen technische IDs/aantallen meegeven, nooit SAS-URLs of foutteksten. */\nexport function logTijdlijn(naam: string, details?: Details): void {\n  if (!ingeschakeld) return;\n  bewaar({ naam, startMs: performance.now() - startTijd, details });\n}\n\nexport function startMeting(naam: string, details?: Details): (status?: string) => void {\n  if (!ingeschakeld) return () => {};\n  const run = generatie;\n  const begin = performance.now();\n  const event: Gebeurtenis = { naam, startMs: begin - startTijd, details };\n  // Ook hangende aanvragen mogen de diagnose niet onbegrensd laten groeien.\n  if (onderweg.size === MAX_EVENTS) {\n    onderweg.delete(onderweg.values().next().value!);\n    weggelaten++;\n  }\n  onderweg.add(event);\n  let afgerond = false;\n  return (status = 'ok') => {\n    if (afgerond) return;\n    afgerond = true;\n    onderweg.delete(event);\n    if (run !== generatie) return;\n    const duurMs = performance.now() - begin;\n    const t = teller(naam);\n    t.aantal++;\n    t.ms += duurMs;\n    t.maxMs = Math.max(t.maxMs, duurMs);\n    if (status === 'error') t.fouten++;\n    bewaar({ ...event, duurMs, status });\n  };\n}\n\nfunction teller(naam: string): Teller {\n  let t = tellers.get(naam);\n  if (!t) {\n    t = { aantal: 0, ms: 0, maxMs: 0, fouten: 0 };\n    tellers.set(naam, t);\n  }\n  return t;\n}\n\n/** Tel een gebeurtenis. */\nexport function tel(naam: string, aantal = 1): void {\n  if (!ingeschakeld) return;\n  teller(naam).aantal += aantal;\n}\n\n/** Meet hoe lang een stuk werk duurt en tel het meteen mee. */\nexport async function meet<T>(naam: string, werk: () => Promise<T>, details?: Details): Promise<T> {\n  if (!ingeschakeld) return werk();\n  const klaar = startMeting(naam, details);\n  try {\n    const resultaat = await werk();\n    klaar();\n    return resultaat;\n  } catch (error) {\n    klaar('error');\n    throw error;\n  }\n}\n\n/** Zelfde, maar voor synchroon werk (de per-pixel loop). */\nexport function meetSync<T>(naam: string, werk: () => T): T {\n  if (!ingeschakeld) return werk();\n  const klaar = startMeting(naam);\n  try {\n    const resultaat = werk();\n    klaar();\n    return resultaat;\n  } catch (error) {\n    klaar('error');\n    throw error;\n  }\n}\n\nfunction reset(): void {\n  generatie++;\n  tellers.clear();\n  gebeurtenissen.length = 0;\n  interacties.length = 0;\n  onderweg.clear();\n  weggelaten = 0;\n  startTijd = performance.now();\n}\n\n/** Extra uitleesfuncties die andere modules aanhangen (zie bathycog-delta). */\nconst diagnoses: Record<string, () => unknown> = {};\n\n/**\n * Hang een uitleesfunctie onder `__tijdlijn`. Bedoeld voor vragen die een\n * teller niet kan beantwoorden — \"wát ligt er in de cache\" in plaats van\n * \"hoeveel\".\n */\nexport function registreerDiagnose(naam: string, fn: () => unknown): () => void {\n  if (!ingeschakeld) return () => {};\n  diagnoses[naam] = fn;\n  if (typeof window !== 'undefined') {\n    const doel = (window as unknown as Record<string, Record<string, unknown>>).__tijdlijn;\n    if (doel) doel[naam] = fn;\n  }\n  return () => {\n    if (diagnoses[naam] !== fn) return;\n    delete diagnoses[naam];\n    if (typeof window !== 'undefined') {\n      const doel = (window as unknown as Record<string, Record<string, unknown>>).__tijdlijn;\n      if (doel) delete doel[naam];\n    }\n  };\n}\n\nfunction snapshot() {\n  const nu = performance.now();\n  return {\n    versie: 1,\n    verstrekenMs: nu - startTijd,\n    weggelaten,\n    tellers: Object.fromEntries([...tellers].map(([naam, t]) => [naam, { ...t }])),\n    gebeurtenissen: [...interacties, ...gebeurtenissen].sort((a, b) => a.startMs - b.startMs),\n    onderweg: [...onderweg].map((e) => ({ ...e, wachtMs: nu - startTijd - e.startMs })),\n    diagnoses: Object.fromEntries(\n      Object.entries(diagnoses).map(([naam, fn]) => {\n        try {\n          return [naam, fn()];\n        } catch {\n          return [naam, { fout: 'diagnose niet beschikbaar' }];\n        }\n      })\n    ),\n  };\n}\n\nfunction download(): void {\n  const url = URL.createObjectURL(\n    new Blob([JSON.stringify(snapshot(), null, 2)], { type: 'application/json' })\n  );\n  const link = document.createElement('a');\n  link.href = url;\n  link.download = `tijdlijn-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;\n  link.click();\n  window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction rapport(): void {\n  const verstreken = Math.round(performance.now() - startTijd);\n  const regels = [...tellers.entries()]\n    .sort((a, b) => b[1].ms - a[1].ms || b[1].aantal - a[1].aantal)\n    .map(([naam, t]) => ({\n      meetpunt: naam,\n      aantal: t.aantal,\n      'totaal ms': Math.round(t.ms),\n      'ms per keer': t.aantal ? Math.round((t.ms / t.aantal) * 10) / 10 : 0,\n      'max ms': Math.round(t.maxMs),\n      fouten: t.fouten,\n    }));\n\n  console.log(`[tijdlijn] ${verstreken} ms sinds reset`);\n\n  console.table(regels);\n  console.log(\n    '[tijdlijn] Parallelle en geneste tijden overlappen; totalen zijn geen wachttijd.',\n    snapshot()\n  );\n}\n\nif (ingeschakeld && typeof window !== 'undefined') {\n  (window as unknown as Record<string, unknown>).__tijdlijn = {\n    reset,\n    rapport,\n    snapshot,\n    // Lightweight polling for the browser benchmark. Full snapshots also walk\n    // every cached tile, which would add work to the interaction being measured.\n    interacties: () => interacties.map((e) => ({ ...e })),\n    verstrekenMs: () => performance.now() - startTijd,\n    download,\n    tellers,\n    ...diagnoses,\n  };\n  startTijd = performance.now();\n  // Browser-netwerktijden zonder URLs, querystrings of credentials te bewaren.\n  // Cross-origin timingdetails kunnen ontbreken zonder Timing-Allow-Origin.\n  if (typeof PerformanceObserver !== 'undefined') {\n    for (const type of ['resource', 'longtask']) {\n      if (!PerformanceObserver.supportedEntryTypes?.includes(type)) continue;\n      const observer = new PerformanceObserver((list) => {\n        for (const entry of list.getEntries()) {\n          if (entry.startTime < startTijd) continue;\n          if (entry.entryType === 'longtask') {\n            bewaar({\n              naam: 'browser.longtask',\n              startMs: entry.startTime - startTijd,\n              duurMs: entry.duration,\n            });\n            continue;\n          }\n          const resource = entry as PerformanceResourceTiming;\n          let url: URL;\n          try {\n            url = new URL(resource.name);\n          } catch {\n            continue;\n          }\n          const api = url.origin === window.location.origin && url.pathname.startsWith('/api/');\n          const cog = /\\.tiff?$/i.test(url.pathname);\n          if (!api && !cog) continue;\n          const detailsBeschikbaar = resource.responseStart > 0;\n          bewaar({\n            naam: api ? 'netwerk.api' : 'netwerk.cog',\n            startMs: resource.startTime - startTijd,\n            duurMs: resource.duration,\n            details: {\n              timingDetails: detailsBeschikbaar,\n              ttfbMs: detailsBeschikbaar ? resource.responseStart - resource.requestStart : null,\n              downloadMs: detailsBeschikbaar ? resource.responseEnd - resource.responseStart : null,\n              transferBytes: detailsBeschikbaar ? resource.transferSize : null,\n            },\n          });\n        }\n      });\n      observer.observe({ type });\n    }\n  }\n\n  console.log('[tijdlijn] meting staat aan — gebruik __tijdlijn.reset() en __tijdlijn.rapport()');\n}\n","/**\n * Bereken de `date_from` voor een N-maanden venster.\n *\n * @param firstOfMonth ISO-datum `YYYY-MM-01` van de geselecteerde maand.\n * @param months       1 = alleen die maand; N>1 = die maand + (N−1) voorgaande;\n *                     0 of negatief = onbeperkt (returns undefined).\n * @returns ISO-datum `YYYY-MM-01` van de eerste maand in het venster, of undefined.\n */\nexport function retentionStartFromMonths(\n  firstOfMonth: string,\n  months: number\n): string | undefined {\n  if (months <= 0) return undefined;\n  const d = new Date(firstOfMonth + 'T00:00:00');\n  d.setMonth(d.getMonth() - (months - 1), 1);\n  const y = d.getFullYear();\n  const m = String(d.getMonth() + 1).padStart(2, '0');\n  return `${y}-${m}-01`;\n}\n\n// Hard-coded array to avoid Node ICU version differences (\"feb.\" vs \"feb\").\nconst NL_MONTHS_SHORT = [\n  'jan', 'feb', 'mrt', 'apr', 'mei', 'jun',\n  'jul', 'aug', 'sep', 'okt', 'nov', 'dec',\n];\n\nconst NL_MONTHS_FULL = [\n  'januari', 'februari', 'maart', 'april', 'mei', 'juni',\n  'juli', 'augustus', 'september', 'oktober', 'november', 'december',\n];\n\nfunction monthYear(d: Date): string {\n  return `${NL_MONTHS_SHORT[d.getMonth()]} ${d.getFullYear()}`;\n}\n\nfunction monthOnly(d: Date): string {\n  return NL_MONTHS_SHORT[d.getMonth()];\n}\n\n/**\n * Format het retention-bereik-label voor de timeline-balk.\n *\n * @param selectedFirstOfMonth ISO-datum `YYYY-MM-01` van de geselecteerde maand.\n * @param months 1 = alleen die maand (\"feb 2026\"); 0 = alles (\"t/m feb 2026\");\n *               N>1 = bereik (\"dec 2025 – feb 2026\" of \"mrt – mei 2026\").\n *               Negatieve waarden zijn niet gedefinieerd (caller responsibility).\n */\nexport function formatRetentionRange(\n  selectedFirstOfMonth: string,\n  months: number\n): string {\n  const end = new Date(selectedFirstOfMonth + 'T00:00:00');\n  if (months === 1) return monthYear(end);\n  if (months === 0) return `t/m ${monthYear(end)}`;\n  const start = new Date(end);\n  start.setMonth(start.getMonth() - (months - 1), 1);\n  if (start.getFullYear() === end.getFullYear()) {\n    return `${monthOnly(start)} – ${monthYear(end)}`;\n  }\n  return `${monthYear(start)} – ${monthYear(end)}`;\n}\n\n/** Korte maandnaam (\"okt\"), voor labels onder de tijdlijnstreepjes. */\nexport function formatMaandKort(firstOfMonth: string): string {\n  return monthOnly(new Date(firstOfMonth + 'T00:00:00'));\n}\n\n/**\n * Eén maand voluit (\"oktober 2026\"). Waar er ruimte is voor één maand, leest de\n * volle naam rustiger dan de afkorting; een bereik blijft kort, anders past het niet.\n */\nexport function formatMaandVoluit(firstOfMonth: string): string {\n  const d = new Date(firstOfMonth + 'T00:00:00');\n  return `${NL_MONTHS_FULL[d.getMonth()]} ${d.getFullYear()}`;\n}\n","import { useEffect, useState } from 'react';\n\nimport type { SurveyDataset } from '@/js/api';\n\nimport { apiDatasetsCogUrlRetrieve, apiDatasetsList } from '@/js/api';\nimport { logTijdlijn, meet, tel } from '@/js/lib/tijdlijn-meting';\nimport { retentionStartFromMonths } from '@/js/lib/timeline-range';\n\n/** Slim list rows carry a server-generated SAS URL (slim=true&include_cog_url=true). */\ntype SlimDataset = SurveyDataset & {\n  cog_url?: string | null;\n  display_cog_url?: string | null;\n  /** Display URL of `vorige_peiling_id`, for the difference map. */\n  vorige_display_cog_url?: string | null;\n};\n\n/** Seed the SAS URL cache from cog_url fields included in a list response. */\nfunction seedSasCache(datasets: SurveyDataset[]): void {\n  for (const ds of datasets) {\n    const url = (ds as SlimDataset).cog_url;\n    if (url) {\n      sasCache.set(ds.id, { url, fetchedAt: Date.now() });\n      displaySasCache.set(ds.id, {\n        url: (ds as SlimDataset).display_cog_url || url,\n        fetchedAt: Date.now(),\n      });\n    }\n    // The previous survey is not in a latest-per-vak list; without this the\n    // difference map would request a cog-url per vak.\n    const vorigeUrl = (ds as SlimDataset).vorige_display_cog_url;\n    if (vorigeUrl && ds.vorige_peiling_id != null) {\n      displaySasCache.set(ds.vorige_peiling_id, { url: vorigeUrl, fetchedAt: Date.now() });\n    }\n  }\n}\n\n/** Return the last day of the month for an ISO date string. */\nexport function endOfMonth(iso: string): string {\n  // Puur op de kalender rekenen: via toISOString (UTC) viel in Nederlandse tijd\n  // de laatste dag van de maand een dag te vroeg, zodat een peiling op de 31e\n  // buiten haar eigen maand viel.\n  const [jaar, maand] = iso.split('-').map(Number);\n  const laatsteDag = new Date(Date.UTC(jaar, maand, 0)).getUTCDate();\n  return `${jaar}-${String(maand).padStart(2, '0')}-${String(laatsteDag).padStart(2, '0')}`;\n}\n\n/** Dataset list cache — keyed by \"date|monthsM\". */\n/**\n * De voorspellingsmaanden (maand-start, bv. oktober en november). In zo'n maand\n * toont de kaart per vak de meting van die maand als die er al is, anders de\n * voorspelling. Gezet door `useSurveyDates` uit `/api/datasets/survey-dates/`.\n * Voor die stap haalt `fetchDatasets` de voorspellingen op in plaats van de\n * metingen; de rest van de kaart (lagen, cursor, doorsnede) werkt ongewijzigd.\n */\nlet voorspellingsDatums = new Set<string>();\n\nexport function setVoorspellingsDatums(datums: string[]): void {\n  voorspellingsDatums = new Set(datums);\n}\n\nexport function isVoorspellingsDatum(datum: string | null | undefined): boolean {\n  return datum != null && voorspellingsDatums.has(datum);\n}\n\nconst datasetsCache = new Map<string, SurveyDataset[]>();\nconst datasetTimes = new Map<string, number>();\nconst DATASET_TTL_MS = 20 * 60 * 1000;\nconst datasetsInFlight = new Map<string, Promise<SurveyDataset[]>>();\n\nfunction datasetsCacheKey(date: string, retentionMonths: number): string {\n  return `${date}|${retentionMonths}m`;\n}\n\nexport async function fetchDatasets(\n  surveyDate: string,\n  retentionMonths: number\n): Promise<SurveyDataset[]> {\n  const key = datasetsCacheKey(surveyDate, retentionMonths);\n  const cached =\n    Date.now() - (datasetTimes.get(key) ?? -Infinity) < DATASET_TTL_MS\n      ? datasetsCache.get(key)\n      : undefined;\n  if (cached) {\n    tel('datasetlijst uit cache');\n    return cached;\n  }\n  const existing = datasetsInFlight.get(key);\n  if (existing) {\n    tel('datasetlijst: lopende aanvraag gedeeld');\n    return existing;\n  }\n  tel('datasetlijst opgehaald');\n\n  const queryDateTo = endOfMonth(surveyDate);\n  const metingQuery = {\n    status: 'ready' as const,\n    date_from: isVoorspellingsDatum(surveyDate)\n      ? surveyDate\n      : retentionStartFromMonths(surveyDate, retentionMonths),\n    date_to: queryDateTo,\n    latest_per_vak: true,\n    slim: true,\n    include_cog_url: true,\n    limit: 200,\n  };\n  // Een voorspellingsmaand: per vak de meting van die maand als die er al is,\n  // anders de voorspelling.\n  const ophalen = isVoorspellingsDatum(surveyDate)\n    ? async () => {\n        const [metingen, voorspellingen] = await Promise.all([\n          apiDatasetsList({ throwOnError: true, query: metingQuery }),\n          apiDatasetsList({\n            throwOnError: true,\n            query: {\n              status: 'ready' as const,\n              peilsoort: ['voorspelling' as const],\n              date_from: surveyDate,\n              date_to: queryDateTo,\n              slim: true,\n              include_cog_url: true,\n              limit: 200,\n            },\n          }),\n        ]);\n        const gemeten = metingen.data?.results ?? [];\n        const vakken = new Set(gemeten.map((ds) => ds.baggervak_code));\n        const aanvulling = (voorspellingen.data?.results ?? []).filter(\n          (ds) => !vakken.has(ds.baggervak_code)\n        );\n        return { data: { results: [...gemeten, ...aanvulling] } };\n      }\n    : () => apiDatasetsList({ throwOnError: true, query: metingQuery });\n  const promise = meet('api.datasetlijst', ophalen, {\n    datum: surveyDate,\n    retentionMonths,\n  })\n    .then(({ data }) => {\n      if (!data?.results) throw new Error('Datasetlijst ontbreekt in antwoord');\n      logTijdlijn('api.datasetlijst.resultaat', {\n        datum: surveyDate,\n        fout: false,\n        datasets: data?.results?.length ?? 0,\n      });\n      const results = data?.results ?? [];\n      seedSasCache(results);\n      datasetsCache.set(key, results);\n      datasetTimes.set(key, Date.now());\n      return results;\n    })\n    .catch((error) => {\n      logTijdlijn('api.datasetlijst.resultaat', { datum: surveyDate, fout: true });\n      throw error;\n    })\n    .finally(() => datasetsInFlight.delete(key));\n\n  datasetsInFlight.set(key, promise);\n  return promise;\n}\n\n/**\n * Prefetch de datasetlijst voor één datum (fire-and-forget). De slim-respons\n * draagt de SAS-URL's mee, dus dit vult meteen de URL-cache.\n *\n * Bewust alléén de lijst. Dit warmde eerder ook de COG-header van élke peiling\n * in het venster — een stuk of dertig aanvragen per datumstap, ongeacht waar je\n * naar keek, die concurreerden met de tegels van de maand waar je net heen\n * ging. Het openen van COG's hoort bij het klaarzetten dat wél weet wat er in\n * beeld staat; zie `use-maand-lagen.ts`.\n */\nexport function prefetchDatasetsForDate(date: string, retentionMonths: number): void {\n  fetchDatasets(date, retentionMonths).catch(() => {});\n}\n\n/**\n * Prefetch datasets for adjacent dates (prev + next) in the timeline.\n */\nexport function prefetchAdjacentDates(\n  currentIndex: number,\n  availableDates: string[],\n  retentionMonths: number\n): void {\n  const prev = currentIndex - 1;\n  const next = currentIndex + 1;\n  if (prev >= 0) prefetchDatasetsForDate(availableDates[prev], retentionMonths);\n  if (next < availableDates.length) prefetchDatasetsForDate(availableDates[next], retentionMonths);\n}\n\nexport function useReadyDatasets(surveyDate?: string | null, retentionMonths = 1) {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    if (!surveyDate) {\n      setDatasets([]);\n      setLoading(false);\n      return;\n    }\n\n    let cancelled = false;\n\n    // Check cache synchronously for instant response\n    const key = datasetsCacheKey(surveyDate, retentionMonths);\n    const cached =\n      Date.now() - (datasetTimes.get(key) ?? -Infinity) < DATASET_TTL_MS\n        ? datasetsCache.get(key)\n        : undefined;\n    if (cached) {\n      setDatasets(cached);\n      setLoading(false);\n      return;\n    }\n\n    setLoading(true);\n\n    fetchDatasets(surveyDate, retentionMonths)\n      .then((results) => {\n        if (!cancelled) setDatasets(results);\n      })\n      .catch(() => {\n        if (!cancelled) setDatasets([]);\n      })\n      .finally(() => {\n        if (!cancelled) setLoading(false);\n      });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [surveyDate, retentionMonths]);\n\n  return { datasets, loading };\n}\n\n/** ATP dataset cache — keyed by \"date|monthsM\". */\nconst atpDatasetsCache = new Map<string, SurveyDataset[]>();\nconst atpDatasetTimes = new Map<string, number>();\nconst atpDatasetsInFlight = new Map<string, Promise<SurveyDataset[]>>();\n\nasync function fetchAtpDatasets(\n  surveyDate: string,\n  retentionMonths: number\n): Promise<SurveyDataset[]> {\n  const key = datasetsCacheKey(surveyDate, retentionMonths);\n  const cached =\n    Date.now() - (atpDatasetTimes.get(key) ?? -Infinity) < DATASET_TTL_MS\n      ? atpDatasetsCache.get(key)\n      : undefined;\n  if (cached) return cached;\n  const existing = atpDatasetsInFlight.get(key);\n  if (existing) return existing;\n\n  const queryDateTo = endOfMonth(surveyDate);\n  const promise = apiDatasetsList({\n    query: {\n      status: 'ready',\n      date_from: retentionStartFromMonths(surveyDate, retentionMonths),\n      date_to: queryDateTo,\n      peilsoort: ['atp'],\n      slim: true,\n      include_cog_url: true,\n      limit: 200,\n    },\n  })\n    .then(({ data }) => {\n      const results = data?.results ?? [];\n      seedSasCache(results);\n      atpDatasetsCache.set(key, results);\n      atpDatasetTimes.set(key, Date.now());\n      return results;\n    })\n    .finally(() => atpDatasetsInFlight.delete(key));\n\n  atpDatasetsInFlight.set(key, promise);\n  return promise;\n}\n\n/**\n * Aantoonpeilingen in het gekozen venster. `enabled` volgt de peilsoort-keuze\n * op de kaart, zodat er niets wordt opgehaald wat niet getoond wordt (#113).\n */\nexport function useAtpDatasets(surveyDate?: string | null, retentionMonths = 1, enabled = true) {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    if (!surveyDate || !enabled) {\n      setDatasets([]);\n      setLoading(false);\n      return;\n    }\n    let cancelled = false;\n\n    const key = datasetsCacheKey(surveyDate, retentionMonths);\n    const cached =\n      Date.now() - (atpDatasetTimes.get(key) ?? -Infinity) < DATASET_TTL_MS\n        ? atpDatasetsCache.get(key)\n        : undefined;\n    if (cached) {\n      setDatasets(cached);\n      setLoading(false);\n      return;\n    }\n\n    setLoading(true);\n    fetchAtpDatasets(surveyDate, retentionMonths)\n      .then((results) => {\n        if (!cancelled) setDatasets(results);\n      })\n      .catch(() => {\n        if (!cancelled) setDatasets([]);\n      })\n      .finally(() => {\n        if (!cancelled) setLoading(false);\n      });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [surveyDate, retentionMonths, enabled]);\n\n  return { datasets, loading };\n}\n\n/** SAS URL cache — keyed by dataset ID. Refresh before 60min expiry. */\nconst sasCache = new Map<number, { url: string; fetchedAt: number }>();\nconst displaySasCache = new Map<number, { url: string; fetchedAt: number }>();\nconst sasInFlight = new Map<number, Promise<string | null>>();\nconst SAS_TTL_MS = 50 * 60 * 1000;\n\nexport async function getCogUrl(datasetId: number): Promise<string | null> {\n  const cached = sasCache.get(datasetId);\n  if (cached && Date.now() - cached.fetchedAt < SAS_TTL_MS) {\n    tel('COG URL uit cache');\n    return cached.url;\n  }\n  const existing = sasInFlight.get(datasetId);\n  if (existing) return existing;\n\n  const promise = meet(\n    'api.cog-url',\n    () => apiDatasetsCogUrlRetrieve({ path: { id: datasetId } }),\n    { datasetId }\n  )\n    .then(({ data }) => {\n      // Backend returns {url: string} but OpenAPI types it as SurveyDataset\n      const url = (data as unknown as { url: string })?.url;\n      if (url) {\n        sasCache.set(datasetId, { url, fetchedAt: Date.now() });\n        displaySasCache.set(datasetId, {\n          url: (data as unknown as { display_url?: string })?.display_url || url,\n          fetchedAt: Date.now(),\n        });\n        return url;\n      }\n      return null;\n    })\n    .catch(() => null)\n    .finally(() => sasInFlight.delete(datasetId));\n\n  sasInFlight.set(datasetId, promise);\n  return promise;\n}\n\n/** Map rendering only. Cursor/depth analyses continue to request getCogUrl. */\nexport async function getDisplayCogUrl(datasetId: number): Promise<string | null> {\n  const cached = displaySasCache.get(datasetId);\n  if (cached && Date.now() - cached.fetchedAt < SAS_TTL_MS) return cached.url;\n  const original = await getCogUrl(datasetId);\n  const refreshed = displaySasCache.get(datasetId);\n  return refreshed && Date.now() - refreshed.fetchedAt < SAS_TTL_MS ? refreshed.url : original;\n}\n","/**\n * Kleuren van de verschilkaart t.o.v. de vorige peiling (bestek 2026, art.\n * 32.3.2 lid 2).\n *\n * Divergerend: blauw is verdiept (de bodem ligt nu lager dan bij de vorige\n * peiling), rood is opgehoogd, bijna wit is gelijk gebleven. Loopt van −1 tot\n * +1 m; wat verder gaat krijgt de kleur van het uiteinde.\n *\n * Bewust een eigen module en niet in `palet-verschil.ts`: dat palet zit in de\n * tegelrenderer, en een wijziging daar maakt alle voorbereide NGD- en\n * max.-pakketten ongeldig. Deze kaart heeft geen pakket; de browser rekent hem\n * uit twee peilingen (`bathycog-delta.ts`).\n *\n * De stops zijn ColorBrewer RdBu (7 klassen), kleurenblindveilig. Dichter bij\n * nul liggen ze dichter op elkaar, net als de klassen van de ontwerpdieptes:\n * daar valt het verschil tussen \"veranderd\" en \"gelijk gebleven\".\n */\n\n/** Tot hoeveel meter de schaal doorloopt, naar beide kanten. */\nexport const VERANDERING_BEREIK_M = 1;\n\nexport interface VeranderingStop {\n  /** Verandering in meter; positief is opgehoogd. */\n  meter: number;\n  color: string;\n  rgb: [number, number, number];\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\n/** Van verdiept (−1 m) naar opgehoogd (+1 m). */\nexport const VERANDERING_STOPS: VeranderingStop[] = [\n  { meter: -1, color: '#2166AC' },\n  { meter: -0.5, color: '#67A9CF' },\n  { meter: -0.2, color: '#D1E5F0' },\n  { meter: 0, color: '#F7F7F7' },\n  { meter: 0.2, color: '#FDDBC7' },\n  { meter: 0.5, color: '#EF8A62' },\n  { meter: 1, color: '#B2182B' },\n].map((stop) => ({ ...stop, rgb: hexToRgb(stop.color) }));\n\n/** Kleur bij een verandering in meter (nu min vorige peiling). */\nexport function veranderingToColor(meter: number): [number, number, number] {\n  const stops = VERANDERING_STOPS;\n  if (meter <= stops[0].meter) return stops[0].rgb;\n  for (let i = 0; i < stops.length - 1; i++) {\n    const laag = stops[i];\n    const hoog = stops[i + 1];\n    if (meter <= hoog.meter) {\n      const s = (meter - laag.meter) / (hoog.meter - laag.meter);\n      return [\n        Math.round(laag.rgb[0] + s * (hoog.rgb[0] - laag.rgb[0])),\n        Math.round(laag.rgb[1] + s * (hoog.rgb[1] - laag.rgb[1])),\n        Math.round(laag.rgb[2] + s * (hoog.rgb[2] - laag.rgb[2])),\n      ];\n    }\n  }\n  return stops[stops.length - 1].rgb;\n}\n\n/** Waar een verandering in de legendabalk ligt, van −1 m (0 %) tot +1 m (100 %). */\nexport function veranderingPositie(meter: number): number {\n  const m = Math.max(-VERANDERING_BEREIK_M, Math.min(VERANDERING_BEREIK_M, meter));\n  return ((m + VERANDERING_BEREIK_M) / (2 * VERANDERING_BEREIK_M)) * 100;\n}\n","/**\n * Tegel-meetkunde in EPSG:3857.\n *\n * De COG-bbox'en uit de API staan in Web Mercator en de XYZ-tegels van\n * MapLibre ook, dus voor \"raakt deze tegel dit baggervak?\" is geen projectie\n * nodig — twee rechthoeken vergelijken volstaat.\n */\n\nconst EARTH_CIRCUMFERENCE_M = 40075016.686;\nconst MERCATOR_ORIGIN = EARTH_CIRCUMFERENCE_M / 2; // 20037508.34\n\nexport interface TileIndex {\n  z: number;\n  x: number;\n  y: number;\n}\n\n/** [minX, minY, maxX, maxY] in meters. */\nexport type MercatorBbox = [number, number, number, number];\n\n/** Bbox in Web Mercator (m) voor een XYZ-tegel. */\nexport function tileBboxMercator(z: number, x: number, y: number): MercatorBbox {\n  const size = EARTH_CIRCUMFERENCE_M / Math.pow(2, z);\n  const minX = -MERCATOR_ORIGIN + x * size;\n  const maxX = minX + size;\n  const maxY = MERCATOR_ORIGIN - y * size;\n  const minY = maxY - size;\n  return [minX, minY, maxX, maxY];\n}\n\n/** Overlappen twee bbox'en elkaar? Raken langs een rand telt als overlap. */\nexport function bboxOverlapt(a: MercatorBbox, b: MercatorBbox): boolean {\n  return a[2] >= b[0] && a[0] <= b[2] && a[3] >= b[1] && a[1] <= b[3];\n}\n\n/**\n * Ligt deze tegel over het gebied van een peiling?\n *\n * Zonder bbox valt er niets uit te sluiten en zeggen we `false`: dit wordt\n * gebruikt om vooruit te laden, en dan is te weinig doen beter dan blind alle\n * tegels van alle peilingen renderen.\n */\nexport function tileRaaktDataset(\n  tile: TileIndex,\n  bbox: { minx?: number | null; miny?: number | null; maxx?: number | null; maxy?: number | null }\n): boolean {\n  const { minx, miny, maxx, maxy } = bbox;\n  if (minx == null || miny == null || maxx == null || maxy == null) return false;\n  return bboxOverlapt(tileBboxMercator(tile.z, tile.x, tile.y), [minx, miny, maxx, maxy]);\n}\n","/**\n * Sub 7 (#97): bathymetry-tile wrapper dat per pixel een reference-depth aftrekt.\n *\n * Strategie: de maplibre-cog-protocol library rendert per tile zelf een\n * ImageBitmap via `setColorFunction`. Voor de bathymetrie-COGs voegen we een\n * extra stap toe in de tile-wrapper in MapView.tsx — daar roepen we\n * {@link renderDeltaTile} aan als we een reference-COG URL geregistreerd\n * hebben voor deze bathymetrie-URL. De functie leest de raw pixelwaardes van\n * beide COGs (via `geotiff` direct, want de cog-protocol library exposeert\n * geen public API voor raw tile-reads), trekt ze van elkaar af en past de\n * delta-kleurtabel toe.\n *\n * Referentietegels worden gedeeld tussen datums, inclusief lopende lezingen.\n * De HTTP-cache alleen voorkomt het opnieuw uitpakken/resamplen niet.\n */\n\nimport { fromUrl, Pool } from 'geotiff';\n\nimport { CogBlockCache } from './cog-block-cache';\nimport { differenceToColor, kansBoven, kansToColor } from './depth-colors';\nimport { kaartExperiment } from './kaart-experiment';\nimport { veranderingToColor } from './palet-verandering';\nimport { meet, meetSync, registreerDiagnose, tel } from './tijdlijn-meting';\nimport { tileBboxMercator } from './tile-geometrie';\n\nimport type { Colormap } from './depth-colors';\nimport type { GeoTIFF } from 'geotiff';\n\nconst TILE_SIZE = 256;\n\n// Explicit local preparation seam. Reuse the exact production pixel algorithm,\n// then verify a lossless PNG round-trip before the batch runner writes it.\nif (kaartExperiment('kaartPrepareTiles', [1], 0)) {\n  (window as any).__prepareC5Tile = async (id: number, z: number, x: number, y: number) => {\n    const url = `${window.location.origin}/display/mm1-v1/${id}.tif`;\n    const ref = getReferencePairing(url);\n    if (!ref) throw new Error('Missing C5 reference pairing');\n    const pixels = await deltaTileImageData(url, ref, z, x, y);\n    const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);\n    const context = canvas.getContext('2d')!;\n    context.putImageData(pixels, 0, 0);\n    const blob = await canvas.convertToBlob({ type: 'image/png' });\n    const bitmap = await createImageBitmap(blob);\n    context.clearRect(0, 0, TILE_SIZE, TILE_SIZE);\n    context.drawImage(bitmap, 0, 0);\n    bitmap.close();\n    const decoded = context.getImageData(0, 0, TILE_SIZE, TILE_SIZE).data;\n    for (let i = 0; i < decoded.length; i++)\n      if (decoded[i] !== pixels.data[i]) throw new Error('PNG changed a rendered pixel');\n    const digest = await crypto.subtle.digest('SHA-256', pixels.data);\n    const rgbaHash = [...new Uint8Array(digest)]\n      .map((n) => n.toString(16).padStart(2, '0'))\n      .join('');\n    const bytes = new Uint8Array(await blob.arrayBuffer());\n    let binary = '';\n    for (const byte of bytes) binary += String.fromCharCode(byte);\n    return { png: btoa(binary), rgbaHash };\n  };\n}\ntype RawTile = Float32Array | Float64Array | Int16Array | Int32Array;\n\ntype GeoTiffEntry = { tiff: Promise<GeoTIFF> };\nconst geotiffCache = new Map<string, GeoTiffEntry>();\n\ntype Metadata = { scale: number; offset: number; noData: number | undefined };\nconst metadataCache = new Map<string, Promise<Metadata>>();\n\n// Koppeling bathymetrie-URL → reference-URL (per baggervak).\n// Key = bathymetrie SAS-URL (zonder cog://-prefix en zonder hash). Deze URL\n// bestaat per dataset en wordt ~60 min gecachet in use-datasets.ts, ruim\n// voldoende voor een kaartsessie.\nconst referencePairing = new Map<string, string>();\n\nexport function setReferencePairing(bathyCogUrl: string, refCogUrl: string): void {\n  referencePairing.set(bathyCogUrl, refCogUrl);\n}\n\nexport function clearReferencePairing(bathyCogUrl: string): void {\n  referencePairing.delete(bathyCogUrl);\n}\n\nexport function getReferencePairing(bathyCogUrl: string): string | undefined {\n  return referencePairing.get(bathyCogUrl);\n}\n\n/**\n * De kleurschaal waarmee dit pad zijn tegels inkleurt.\n *\n * Staat op moduleniveau en niet als parameter, omdat de aanroeper de\n * tile-wrapper van MapLibre is: die krijgt alleen een URL en een tegelindex\n * mee. Zelfde opzet als {@link setReferencePairing} hierboven.\n *\n * Gaat mee in de cachesleutel. Zonder dat blijft na een wissel de tegel van de\n * vorige schaal liggen en verandert er niets zichtbaars.\n */\nlet deltaColormap: Colormap = 'continu';\n\nexport function setDeltaColormap(colormap: Colormap): void {\n  deltaColormap = colormap;\n}\n\nexport function getDeltaColormap(): Colormap {\n  return deltaColormap;\n}\n\n/**\n * Of de gekoppelde tweede COG de vorige peiling van hetzelfde vak is in plaats\n * van een referentievlak: de verschilkaart (bestek 2026, art. 32.3.2 lid 2).\n *\n * Dan is `diepte − referentie` de verandering sinds die peiling, en kleurt het\n * divergerend (`palet-verandering.ts`), los van de gekozen kleurschaal: klassen,\n * verloop en kans horen bij een ontwerpdiepte. Zelfde opzet en dezelfde reden\n * voor de cachesleutel als {@link setDeltaColormap}.\n */\nlet deltaVerschil = false;\n\nexport function setDeltaVerschil(aan: boolean): void {\n  deltaVerschil = aan;\n}\n\nexport function getDeltaVerschil(): boolean {\n  return deltaVerschil;\n}\n\n/**\n * Maandspreiding σ (m) van het vak waar deze peiling bij hoort, voor de\n * kleurschaal `kans` (WP6). Zelfde opzet als {@link setReferencePairing}: de\n * tile-wrapper krijgt alleen een URL, dus de koppeling staat hier.\n *\n * Geen σ betekent een vak met te weinig maandparen; de kanskaart tekent dat\n * vak dan niet, in plaats van een kans te verzinnen.\n */\nconst spreidingPerUrl = new Map<string, number>();\n\nexport function setSpreiding(bathyCogUrl: string, sigma: number | null | undefined): void {\n  if (sigma != null && sigma > 0) spreidingPerUrl.set(bathyCogUrl, sigma);\n  else spreidingPerUrl.delete(bathyCogUrl);\n}\n\nexport function getSpreiding(bathyCogUrl: string): number | undefined {\n  return spreidingPerUrl.get(bathyCogUrl);\n}\n\n/** Eén gedeelde Pool voor geotiff decompressie — voorkomt honderden workers. */\nlet pool: Pool | undefined;\nfunction getPool(): Pool {\n  if (!pool) {\n    pool = new Pool();\n    const decode = pool.decode.bind(pool);\n    pool.decode = (directory, buffer) =>\n      meet('COG.decode-inclusief-workerwachttijd', () => decode(directory, buffer));\n  }\n  return pool;\n}\n\n/**\n * LRU-cache van gerenderde delta-tiles (rgba-buffers, géén ImageBitmaps —\n * MapLibre mag een doorgegeven bitmap sluiten na GPU-upload). Hierdoor is\n * terugkeren naar een eerder bekeken datum vrijwel instant: geen raster-reads\n * en geen per-pixel loop meer.\n *\n * Twee gescheiden ruimtes, en dat is het hele punt. Eén gedeelde LRU liep leeg\n * op zichzelf: het vooruitladen schrijft veel meer tegels weg dan de kaart\n * opvraagt (gemeten 683 om 411), en tegels die in beeld staan worden door\n * MapLibre niet opnieuw opgevraagd — dus ze verversen hun plek in de LRU niet\n * en sneuvelden als eerste. Resultaat: 296 verdringingen in negen minuten, en\n * precies de maand waar je naar terug wilde was weg.\n *\n * Nu kan het vooruitladen alleen zijn eigen ruimte vullen. Wordt een\n * vooruitgeladen tegel echt opgevraagd, dan verhuist hij naar de levende kant.\n *\n * De budgetten staan in bytes omdat dat de grootheid is die ertoe doet: één\n * tegel is 256 × 256 × 4 = 256 KiB.\n */\nconst TILE_BYTES = TILE_SIZE * TILE_SIZE * 4;\n\n/**\n * Hoeveel geheugen de tegelcache mag kosten.\n *\n * Uitgezoomd kost één datum al gauw honderd tegels; met 160 MB paste er dan\n * een stuk of vijf in en werd bij een veeg over de hele tijdlijn het begin\n * verdrongen voordat je terugkwam. Meer ruimte helpt daar direct tegen.\n *\n * Maar dit is geen gratis knop: het gaat om echte bytes in het tabblad, naast\n * wat MapLibre zelf al vasthoudt. Een vaste grens van 1 GB werkt prima op een\n * werkstation en sloopt een laptop met 8 GB — en dan is de kaart niet traag\n * maar weg. Vandaar meeschalen met wat de machine zegt te hebben, met 1 GB als\n * plafond. `deviceMemory` is grof (afgerond, en gemaximeerd op 8) maar precies\n * genoeg om het verschil tussen een werkstation en een instaplaptop te zien.\n * Browsers die het niet melden krijgen de middenmoot.\n */\nfunction cacheBudgetBytes(): number {\n  const gb = (navigator as unknown as { deviceMemory?: number }).deviceMemory ?? 4;\n  const achtste = gb * 128 * 1024 * 1024;\n  return Math.min(1024 * 1024 * 1024, Math.max(128 * 1024 * 1024, achtste));\n}\n\nconst CACHE_BUDGET = cacheBudgetBytes();\n// Reserveer maximaal 32 MiB binnen het bestaande budget voor referentierasters.\nconst REFERENCE_CACHE_BYTES = Math.min(32 * 1024 * 1024, CACHE_BUDGET * 0.1);\nconst BLOCK_CACHE_BYTES = Math.min(64 * 1024 * 1024, CACHE_BUDGET * 0.2);\nconst RENDER_CACHE_BYTES = CACHE_BUDGET - REFERENCE_CACHE_BYTES - BLOCK_CACHE_BYTES;\nconst decodedBlocks = new CogBlockCache(BLOCK_CACHE_BYTES);\n// Het grootste deel voor wat je bekijkt; de rest voor wat klaargezet wordt.\nconst LEVEND_CACHE_MAX = Math.floor((RENDER_CACHE_BYTES * 0.6) / TILE_BYTES);\nconst VOORUIT_CACHE_MAX = Math.floor((RENDER_CACHE_BYTES * 0.4) / TILE_BYTES);\nconst referenceTiles = new Map<string, RawTile>();\nconst referenceReads = new Map<string, Promise<RawTile>>();\nlet referenceBytes = 0;\n\n/** Hoeveel tegels er vooruitgeladen mogen worden voordat het zichzelf verdringt. */\nexport function vooruitCapaciteit(): number {\n  return VOORUIT_CACHE_MAX;\n}\n\n/** Tegels die de kaart daadwerkelijk heeft opgevraagd. */\nconst levendeTiles = new Map<string, ImageData>();\n/** Tegels die alleen vooruit zijn geladen; mogen de levende nooit verdringen. */\nconst vooruitTiles = new Map<string, ImageData>();\n/**\n * Renders die nú onderweg zijn. De kaart en het vooruitladen vragen dezelfde\n * tegels op vrijwel hetzelfde moment aan; zonder dit register deden ze\n * allebei de volledige dubbele rasterlezing — dubbel werk precies wanneer het\n * netwerk toch al vol zit.\n */\nconst lopendeRenders = new Map<string, Promise<ImageData>>();\n\nfunction verversPositie(cache: Map<string, ImageData>, key: string, waarde: ImageData): void {\n  cache.delete(key);\n  cache.set(key, waarde);\n}\n\nfunction snoei(cache: Map<string, ImageData>, max: number, naam: string): void {\n  while (cache.size >= max) {\n    const oudste = cache.keys().next().value;\n    if (oudste === undefined) return;\n    cache.delete(oudste);\n    tel(`deltatile-cache: verdrongen (${naam})`);\n  }\n}\n\n/**\n * Zoek een tegel op. `vooruit` geeft aan of dit een vooruitlaad-opvraag is;\n * een echte opvraag promoveert een vooruitgeladen tegel naar de levende kant.\n */\nfunction deltaTileCacheGet(key: string, vooruit: boolean): ImageData | undefined {\n  const levend = levendeTiles.get(key);\n  if (levend) {\n    verversPositie(levendeTiles, key, levend);\n    return levend;\n  }\n  const klaargezet = vooruitTiles.get(key);\n  if (!klaargezet) return undefined;\n  if (vooruit) {\n    verversPositie(vooruitTiles, key, klaargezet);\n    return klaargezet;\n  }\n  vooruitTiles.delete(key);\n  snoei(levendeTiles, LEVEND_CACHE_MAX, 'levend');\n  levendeTiles.set(key, klaargezet);\n  return klaargezet;\n}\n\nfunction deltaTileCacheSet(key: string, rgba: ImageData, vooruit: boolean): void {\n  if (vooruit) {\n    snoei(vooruitTiles, VOORUIT_CACHE_MAX, 'vooruit');\n    vooruitTiles.set(key, rgba);\n    return;\n  }\n  snoei(levendeTiles, LEVEND_CACHE_MAX, 'levend');\n  levendeTiles.set(key, rgba);\n}\n\n/** Ligt deze tegel al klaar? Laat het vooruitladen werk overslaan zonder kosten. */\nexport function heeftDeltaTile(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number\n): boolean {\n  const key = cacheSleutel(bathyCogUrl, refCogUrl, z, x, y, huidigeKleurstand(bathyCogUrl));\n  return levendeTiles.has(key) || vooruitTiles.has(key);\n}\n\n/** Test-only: leeg de rendered-tile cache. */\nexport function __clearDeltaTileCacheForTests(): void {\n  levendeTiles.clear();\n  vooruitTiles.clear();\n  lopendeRenders.clear();\n  referenceTiles.clear();\n  referenceReads.clear();\n  referenceBytes = 0;\n}\n\n/**\n * Diagnose: wélke tegels liggen er klaar, per COG.\n *\n * Een teller zegt hoevéél er is klaargezet, en dat bleek niet genoeg: het\n * vooruitladen deed zijn werk terwijl de kaart er vrijwel niets van terugvond.\n * Dan is de vraag welke tegels, niet hoeveel.\n */\nfunction cacheOverzicht(): Record<string, Record<string, string[]>> {\n  const groepeer = (m: Map<string, ImageData>) => {\n    const uit: Record<string, string[]> = {};\n    for (const sleutel of m.keys()) {\n      const [bathy, , , tegel] = sleutel.split('|');\n      const naam = bathy.split('?')[0].split('/').pop() || bathy;\n      (uit[naam] ||= []).push(tegel);\n    }\n    return uit;\n  };\n  return { levend: groepeer(levendeTiles), vooruit: groepeer(vooruitTiles) };\n}\n\nregistreerDiagnose('cache', cacheOverzicht);\n\n/**\n * De kleurinstellingen waarmee een tegel getekend wordt, vastgelegd op het\n * moment van de aanvraag. De render leest ze pas na het wachten op de\n * rasterlezingen; wisselt de gebruiker intussen van kaartbeeld of kleurschaal,\n * dan moet de tegel toch de kleuren krijgen die bij zijn cachesleutel horen.\n */\ninterface Kleurstand {\n  verschil: boolean;\n  colormap: Colormap;\n  /** Alleen bij de kleurschaal `kans`; `undefined` als het vak geen σ heeft. */\n  sigma: number | undefined;\n}\n\nfunction huidigeKleurstand(bathyCogUrl: string): Kleurstand {\n  const kans = !deltaVerschil && deltaColormap === 'kans';\n  return {\n    verschil: deltaVerschil,\n    colormap: deltaColormap,\n    sigma: kans ? getSpreiding(bathyCogUrl) : undefined,\n  };\n}\n\nfunction cacheSleutel(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number,\n  stand: Kleurstand\n): string {\n  // Bij `kans` hoort σ in de sleutel: na een nieuwe peiling verandert de\n  // spreiding van een vak, en dan mag de tegel van de oude σ niet blijven.\n  const schaal = stand.verschil\n    ? 'verschil'\n    : stand.colormap === 'kans'\n      ? `kans:${stand.sigma ?? '-'}`\n      : stand.colormap;\n  return `${bathyCogUrl}|${refCogUrl}|${schaal}|${z}/${x}/${y}`;\n}\n\n/**\n * Pre-open een COG: haal de header + metadata op zodat een latere tile-render\n * alleen nog tile-data hoeft te fetchen. Fire-and-forget vanuit de\n * timeline-prefetch (use-datasets.ts).\n */\nexport function warmCogHeader(url: string): void {\n  getCogMeta(url).catch(() => {\n    // Prefetch-fout is onschuldig — de echte render probeert het opnieuw.\n    metadataCache.delete(url);\n    geotiffCache.delete(url);\n  });\n}\n\nfunction getGeoTiff(url: string): Promise<GeoTIFF> {\n  let entry = geotiffCache.get(url);\n  if (!entry) {\n    tel('COG voor het eerst geopend');\n    entry = {\n      tiff: meet('COG.open-header', async () => decodedBlocks.attach(await fromUrl(url), url)),\n    };\n    geotiffCache.set(url, entry);\n  }\n  return entry.tiff;\n}\n\nasync function getCogMeta(url: string): Promise<Metadata> {\n  let existing = metadataCache.get(url);\n  if (!existing) {\n    existing = (async () => {\n      const tiff = await getGeoTiff(url);\n      const image = await tiff.getImage();\n      const gdalMeta = image.getGDALMetadata(0);\n      return {\n        scale: gdalMeta?.SCALE !== undefined ? parseFloat(gdalMeta.SCALE) : 1.0,\n        offset: gdalMeta?.OFFSET !== undefined ? parseFloat(gdalMeta.OFFSET) : 0.0,\n        noData: image.getGDALNoData() ?? undefined,\n      };\n    })();\n    metadataCache.set(url, existing);\n  }\n  return existing;\n}\n\nasync function readRawTile(url: string, z: number, x: number, y: number): Promise<RawTile> {\n  const tiff = await getGeoTiff(url);\n  const { noData } = await getCogMeta(url);\n  // geotiff.readRasters accepteert NaN niet als fillValue; volg library-conventie.\n  const fillValue = noData === undefined || Number.isNaN(noData) ? Infinity : noData;\n  const tile = await tiff.readRasters({\n    bbox: tileBboxMercator(z, x, y),\n    width: TILE_SIZE,\n    height: TILE_SIZE,\n    interleave: true,\n    resampleMethod: 'nearest',\n    pool: getPool(),\n    fillValue,\n  });\n  return tile as unknown as RawTile;\n}\n\n/**\n * Een willekeurig venster uit een COG, als dieptes in meter met NaN voor\n * nodata. Voor lagen die buren nodig hebben, zoals het reliëf: een tegel van\n * 256 pixels plus een rand van één pixel. Deelt de geopende COG's, de\n * metadata en de decoder-pool met het deltapad.\n */\nexport async function leesVenster(\n  url: string,\n  bbox: [number, number, number, number],\n  breedte: number,\n  hoogte: number\n): Promise<Float64Array> {\n  const tiff = await getGeoTiff(url);\n  const { scale, offset, noData } = await getCogMeta(url);\n  const fillValue = noData === undefined || Number.isNaN(noData) ? Infinity : noData;\n  const ruw = (await tiff.readRasters({\n    bbox,\n    width: breedte,\n    height: hoogte,\n    interleave: true,\n    resampleMethod: 'nearest',\n    pool: getPool(),\n    fillValue,\n  })) as unknown as RawTile;\n  const uit = new Float64Array(breedte * hoogte);\n  for (let i = 0; i < uit.length; i++) {\n    const waarde = ruw[i];\n    uit[i] = isNoData(waarde, noData) ? NaN : waarde * scale + offset;\n  }\n  return uit;\n}\n\n/** Dezelfde referentie/XYZ wordt door alle peildatums gebruikt; nooit muteren. */\nasync function readReferenceTile(url: string, z: number, x: number, y: number): Promise<RawTile> {\n  const key = `${url}|${z}/${x}/${y}`;\n  const cached = referenceTiles.get(key);\n  if (cached) {\n    tel('referentietegel: treffer');\n    referenceTiles.delete(key);\n    referenceTiles.set(key, cached);\n    return cached;\n  }\n  const pending = referenceReads.get(key);\n  if (pending) {\n    tel('referentietegel: meegelift');\n    return pending;\n  }\n  const read = meet('rasterlezing referentie', () => readRawTile(url, z, x, y));\n  referenceReads.set(key, read);\n  try {\n    const tile = await read;\n    while (referenceBytes + tile.byteLength > REFERENCE_CACHE_BYTES && referenceTiles.size) {\n      const oldest = referenceTiles.keys().next().value!;\n      referenceBytes -= referenceTiles.get(oldest)!.byteLength;\n      referenceTiles.delete(oldest);\n      tel('referentietegel: verdrongen');\n    }\n    if (tile.byteLength <= REFERENCE_CACHE_BYTES) {\n      referenceTiles.set(key, tile);\n      referenceBytes += tile.byteLength;\n    }\n    return tile;\n  } finally {\n    // Ook een fout moet opnieuw geprobeerd kunnen worden.\n    referenceReads.delete(key);\n  }\n}\n\nfunction isNoData(value: number, noData: number | undefined): boolean {\n  if (!Number.isFinite(value)) return true;\n  if (noData !== undefined && value === noData) return true;\n  return false;\n}\n\n/**\n * Render een bathymetrie-tile als delta t.o.v. de reference-COG voor hetzelfde\n * (z, x, y). Return is een RGBA ImageBitmap klaar voor MapLibre.\n */\nexport async function renderDeltaTile(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number\n): Promise<ImageBitmap> {\n  const pixels = await deltaTileImageData(bathyCogUrl, refCogUrl, z, x, y);\n  return meet('tile.bitmap', () => createImageBitmap(pixels));\n}\n\n/**\n * Zet een delta-tile alvast in de cache, zonder de bitmap te bouwen.\n *\n * Gebruikt door het vooruitladen over de tijdlijn: daar is alleen het gevulde\n * cachevakje het doel. De bitmap wordt pas gemaakt als MapLibre de tegel\n * werkelijk opvraagt.\n */\nexport async function warmDeltaTile(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number\n): Promise<void> {\n  await deltaTileImageData(bathyCogUrl, refCogUrl, z, x, y, true);\n}\n\nasync function deltaTileImageData(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number,\n  vooruit = false\n): Promise<ImageData> {\n  const stand = huidigeKleurstand(bathyCogUrl);\n  const cacheKey = cacheSleutel(bathyCogUrl, refCogUrl, z, x, y, stand);\n  const cachedTile = deltaTileCacheGet(cacheKey, vooruit);\n  if (cachedTile) {\n    tel('deltatile-cache: treffer');\n    return cachedTile;\n  }\n\n  const lopend = lopendeRenders.get(cacheKey);\n  if (lopend) {\n    tel('deltatile-cache: meegelift');\n    const beeld = await lopend;\n    // De eerste aanvrager bepaalde in welke kant de tegel landde; een echte\n    // opvraag hoort hem alsnog naar de levende kant te promoveren.\n    return deltaTileCacheGet(cacheKey, vooruit) ?? beeld;\n  }\n  tel('deltatile-cache: misser');\n\n  const render = renderNaarCache(bathyCogUrl, refCogUrl, z, x, y, vooruit, cacheKey, stand);\n  lopendeRenders.set(cacheKey, render);\n  try {\n    return await render;\n  } finally {\n    lopendeRenders.delete(cacheKey);\n  }\n}\n\nasync function renderNaarCache(\n  bathyCogUrl: string,\n  refCogUrl: string,\n  z: number,\n  x: number,\n  y: number,\n  vooruit: boolean,\n  cacheKey: string,\n  stand: Kleurstand\n): Promise<ImageData> {\n  const [bathyRaw, refRaw, bathyMeta, refMeta] = await Promise.all([\n    meet('rasterlezing bathymetrie', () => readRawTile(bathyCogUrl, z, x, y)),\n    readReferenceTile(refCogUrl, z, x, y),\n    getCogMeta(bathyCogUrl),\n    getCogMeta(refCogUrl),\n  ]);\n\n  const pixels = TILE_SIZE * TILE_SIZE;\n  const rgba = new Uint8ClampedArray(pixels * 4);\n  // Niet de globale instellingen opnieuw lezen: die kunnen tijdens het\n  // wachten hierboven gewisseld zijn (zie `Kleurstand`).\n  const { verschil, colormap, sigma } = stand;\n  const kans = !verschil && colormap === 'kans';\n\n  // Eén band per COG: bestaande peilingen zijn float64, referenties float32.\n  // Interleaved met één band levert één waarde per pixel; behoud de precisie.\n  meetSync('per-pixel loop', () => {\n    for (let i = 0; i < pixels; i++) {\n      const rawB = bathyRaw[i];\n      if (isNoData(rawB, bathyMeta.noData)) {\n        rgba[i * 4 + 3] = 0;\n        continue;\n      }\n      const rawR = refRaw[i];\n      if (isNoData(rawR, refMeta.noData)) {\n        // Buiten een dieptereferentie-overlay (of buiten de vorige peiling):\n        // val terug op transparant zodat de gebruiker ziet dat er geen\n        // referentie beschikbaar is voor deze pixel.\n        rgba[i * 4 + 3] = 0;\n        continue;\n      }\n      if (kans && sigma === undefined) {\n        // Vak zonder spreiding: geen kans te geven, dus niets tekenen.\n        rgba[i * 4 + 3] = 0;\n        continue;\n      }\n      const depth = rawB * bathyMeta.scale + bathyMeta.offset;\n      const reference = rawR * refMeta.scale + refMeta.offset;\n      // σ bestaat alleen bij de kleurschaal `kans`.\n      const [r, g, b] = verschil\n        ? veranderingToColor(depth - reference)\n        : sigma !== undefined\n          ? kansToColor(kansBoven(depth - reference, sigma))\n          : differenceToColor(depth - reference, colormap);\n      rgba[i * 4 + 0] = r;\n      rgba[i * 4 + 1] = g;\n      rgba[i * 4 + 2] = b;\n      rgba[i * 4 + 3] = 255;\n    }\n  });\n\n  const imageData = new ImageData(rgba, TILE_SIZE, TILE_SIZE);\n  deltaTileCacheSet(cacheKey, imageData, vooruit);\n  return imageData;\n}\n","import { meet, tel } from './tijdlijn-meting';\n\nimport type { GeoTIFF, GeoTIFFImage } from 'geotiff';\n\n// GeoTIFF.js 2.1.3 returns this object, although its generated declaration\n// incorrectly says ArrayBuffer. Keep that compatibility detail in this adapter.\ninterface Block {\n  x: number;\n  y: number;\n  sample: number;\n  data: ArrayBuffer;\n}\n\n/** Shared, byte-bounded LRU for decoded TIFF blocks, including in-flight reads.\n * Adjacent XYZ tiles often overlap the same TIFF block. Cache before source.fetch\n * and decoding, without changing overview selection, samples or resampling. */\nexport class CogBlockCache {\n  private ready = new Map<string, Block>();\n  private pending = new Map<string, Promise<Block>>();\n  private bytes = 0;\n\n  constructor(private readonly maxBytes: number) {}\n\n  async read(key: string, load: () => Promise<Block>): Promise<Block> {\n    const cached = this.ready.get(key);\n    if (cached) {\n      this.ready.delete(key);\n      this.ready.set(key, cached);\n      tel('COG-blok: treffer');\n      return cached;\n    }\n    const pending = this.pending.get(key);\n    if (pending) {\n      tel('COG-blok: meegelift');\n      return pending;\n    }\n    tel('COG-blok: misser');\n    const read = Promise.resolve().then(load);\n    this.pending.set(key, read);\n    try {\n      const block = await read;\n      while (this.bytes + block.data.byteLength > this.maxBytes && this.ready.size) {\n        const oldest = this.ready.keys().next().value!;\n        this.bytes -= this.ready.get(oldest)!.data.byteLength;\n        this.ready.delete(oldest);\n        tel('COG-blok: verdrongen');\n      }\n      if (block.data.byteLength <= this.maxBytes) {\n        this.ready.set(key, block);\n        this.bytes += block.data.byteLength;\n      }\n      return block;\n    } finally {\n      this.pending.delete(key);\n    }\n  }\n\n  /** Apply to one GeoTIFF instance; URLs (including version/SAS) isolate files. */\n  attach(tiff: GeoTIFF, url: string): GeoTIFF {\n    const getImage = tiff.getImage.bind(tiff);\n    const images = new Map<number, Promise<GeoTIFFImage>>();\n    tiff.getImage = (index = 0) => {\n      let pending = images.get(index);\n      if (!pending) {\n        pending = getImage(index)\n          .then((image) => {\n            const readBlock = image.getTileOrStrip.bind(image);\n            image.getTileOrStrip = (x, y, sample, decoder, signal) => {\n              // Shared work cannot inherit one consumer's cancellation signal.\n              if (signal) return readBlock(x, y, sample, decoder, signal);\n              return this.read(`${url}|${index}|${x}/${y}/${sample}`, () =>\n                meet(\n                  'COG.blok-ophalen-en-decoderen',\n                  () => readBlock(x, y, sample, decoder) as unknown as Promise<Block>\n                )\n              ) as unknown as ReturnType<GeoTIFFImage['getTileOrStrip']>;\n            };\n            return image;\n          })\n          .catch((error) => {\n            images.delete(index);\n            throw error;\n          });\n        images.set(index, pending);\n      }\n      return pending;\n    };\n    return tiff;\n  }\n}\n","import proj4 from 'proj4';\n\n// Define EPSG:28992 (Amersfoort / RD New)\nproj4.defs(\n  'EPSG:28992',\n  '+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs'\n);\n\nconst RD_TO_WGS84 = proj4('EPSG:28992', 'EPSG:4326');\n\nexport function rdToWgs84(x: number, y: number): [number, number] {\n  return RD_TO_WGS84.forward([x, y]) as [number, number];\n}\n\nexport function wgs84ToRd(lng: number, lat: number): [number, number] {\n  return RD_TO_WGS84.inverse([lng, lat]) as [number, number];\n}\n\n/**\n * Transform all coordinates in a GeoJSON FeatureCollection from EPSG:28992 to WGS84.\n */\nexport function transformGeoJSON(geojson: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection {\n  return {\n    ...geojson,\n    features: geojson.features.map((feature) => ({\n      ...feature,\n      geometry: transformGeometry(feature.geometry),\n    })),\n  };\n}\n\nfunction transformCoord(c: number[]): number[] {\n  const [lng, lat] = rdToWgs84(c[0], c[1]);\n  // Z (3e element) blijft behouden voor PolygonZ-overlays — nodig voor tooltips\n  // en toekomstige 3D-viewer zonder roundtrip via de backend.\n  return c.length >= 3 ? [lng, lat, c[2]] : [lng, lat];\n}\n\nfunction transformGeometry(geometry: GeoJSON.Geometry): GeoJSON.Geometry {\n  switch (geometry.type) {\n    case 'Point':\n      return { ...geometry, coordinates: transformCoord(geometry.coordinates) };\n    case 'MultiPoint':\n    case 'LineString':\n      return { ...geometry, coordinates: geometry.coordinates.map(transformCoord) };\n    case 'MultiLineString':\n    case 'Polygon':\n      return {\n        ...geometry,\n        coordinates: geometry.coordinates.map((ring) => ring.map(transformCoord)),\n      };\n    case 'MultiPolygon':\n      return {\n        ...geometry,\n        coordinates: geometry.coordinates.map((polygon) =>\n          polygon.map((ring) => ring.map(transformCoord))\n        ),\n      };\n    default:\n      return geometry;\n  }\n}\n","/**\n * Rekenwerk voor het meetbeeld van één vak op de Analyse-pagina: de ruwe\n * dieptes uit de peiling, gedraaid zodat de lange as van het vak horizontaal\n * ligt (dezelfde draaiing als de oude heatmap, `obb_angle_deg` in RD).\n *\n * Losse functies zonder DOM, zodat ze te toetsen zijn; het tekenen zit in\n * `pages/analyse/VakMeting.tsx`.\n */\n\nimport { depthToColor } from './depth-colors';\nimport { rdToWgs84 } from './projections';\n\nconst AARDSTRAAL = 6378137;\n\nexport type Ring = ReadonlyArray<readonly [number, number]>;\n\n/** Lengtegraad/breedtegraad naar EPSG:3857, het stelsel van de display-COG. */\nexport function naarMercator(lng: number, lat: number): [number, number] {\n  const x = (AARDSTRAAL * lng * Math.PI) / 180;\n  const y = AARDSTRAAL * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));\n  return [x, y];\n}\n\n/**\n * Affiene afbeelding van een canvaspixel (i, j) naar EPSG:3857. Binnen één vak\n * (een paar km) is RD → Mercator op een paar cm na lineair, dus drie punten\n * zijn genoeg en hoeft er niet per pixel geprojecteerd te worden.\n */\nexport interface PixelNaarMercator {\n  x0: number;\n  y0: number;\n  dxi: number;\n  dyi: number;\n  dxj: number;\n  dyj: number;\n}\n\nexport interface VakKader {\n  /** Ringen in het vakeigen stelsel: lange as langs +x, midden op (0, 0), in m. */\n  ringen: Array<Array<[number, number]>>;\n  breedte: number;\n  hoogte: number;\n  /** Midden van het kader in RD. */\n  cx: number;\n  cy: number;\n  hoek: number;\n}\n\nexport function vakKader(ringenRd: ReadonlyArray<Ring>, hoekGraden: number): VakKader {\n  const hoek = (hoekGraden * Math.PI) / 180;\n  const cos = Math.cos(-hoek);\n  const sin = Math.sin(-hoek);\n  const punten = ringenRd.flat();\n  const mx = punten.reduce((s, p) => s + p[0], 0) / punten.length;\n  const my = punten.reduce((s, p) => s + p[1], 0) / punten.length;\n  const lokaal = ringenRd.map((r) =>\n    r.map(([x, y]): [number, number] => {\n      const dx = x - mx;\n      const dy = y - my;\n      return [dx * cos - dy * sin, dx * sin + dy * cos];\n    })\n  );\n  const xs = lokaal.flat().map((p) => p[0]);\n  const ys = lokaal.flat().map((p) => p[1]);\n  const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)];\n  // Centreer op het kader, niet op het gemiddelde van de hoekpunten.\n  const ox = (x0 + x1) / 2;\n  const oy = (y0 + y1) / 2;\n  const terug = (lx: number, ly: number): [number, number] => [\n    mx + lx * Math.cos(hoek) - ly * Math.sin(hoek),\n    my + lx * Math.sin(hoek) + ly * Math.cos(hoek),\n  ];\n  const [cx, cy] = terug(ox, oy);\n  return {\n    ringen: lokaal.map((r) => r.map(([x, y]): [number, number] => [x - ox, y - oy])),\n    breedte: x1 - x0,\n    hoogte: y1 - y0,\n    cx,\n    cy,\n    hoek,\n  };\n}\n\n/** Van vakeigen meters (lx, ly) naar RD. */\nfunction lokaalNaarRd(k: VakKader, lx: number, ly: number): [number, number] {\n  return [\n    k.cx + lx * Math.cos(k.hoek) - ly * Math.sin(k.hoek),\n    k.cy + lx * Math.sin(k.hoek) + ly * Math.cos(k.hoek),\n  ];\n}\n\n/**\n * Canvas van `breedte` × `hoogte` px met het vak gecentreerd op `schaal` px/m.\n * Pixel (i, j) heeft vakeigen coördinaat ((i + ½ − B/2)/schaal, (H/2 − j − ½)/schaal).\n */\nexport function pixelNaarMercator(\n  k: VakKader,\n  breedte: number,\n  hoogte: number,\n  schaal: number,\n  rdNaarMerc: (x: number, y: number) => [number, number] = (x, y) =>\n    naarMercator(...rdToWgs84(x, y))\n): PixelNaarMercator {\n  const lokaal = (i: number, j: number) =>\n    lokaalNaarRd(k, (i + 0.5 - breedte / 2) / schaal, (hoogte / 2 - j - 0.5) / schaal);\n  const p00 = rdNaarMerc(...lokaal(0, 0));\n  const p10 = rdNaarMerc(...lokaal(1, 0));\n  const p01 = rdNaarMerc(...lokaal(0, 1));\n  return {\n    x0: p00[0],\n    y0: p00[1],\n    dxi: p10[0] - p00[0],\n    dyi: p10[1] - p00[1],\n    dxj: p01[0] - p00[0],\n    dyj: p01[1] - p00[1],\n  };\n}\n\n/** Het Mercator-kader dat het hele canvas dekt: [xmin, ymin, xmax, ymax]. */\nexport function mercatorKader(\n  a: PixelNaarMercator,\n  breedte: number,\n  hoogte: number\n): [number, number, number, number] {\n  const hoeken = [\n    [0, 0],\n    [breedte, 0],\n    [0, hoogte],\n    [breedte, hoogte],\n  ].map(([i, j]) => [a.x0 + i * a.dxi + j * a.dxj, a.y0 + i * a.dyi + j * a.dyj]);\n  const xs = hoeken.map((p) => p[0]);\n  const ys = hoeken.map((p) => p[1]);\n  return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];\n}\n\n/** Ligt (x, y) binnen de ringen (even-oneven, dus gaten tellen mee)? */\nexport function binnen(ringen: ReadonlyArray<Ring>, x: number, y: number): boolean {\n  let raak = false;\n  for (const ring of ringen) {\n    for (let a = 0, b = ring.length - 1; a < ring.length; b = a++) {\n      const [xa, ya] = ring[a];\n      const [xb, yb] = ring[b];\n      if (ya > y !== yb > y && x < ((xb - xa) * (y - ya)) / (yb - ya) + xa) raak = !raak;\n    }\n  }\n  return raak;\n}\n\n/**\n * Kleurbereik van 2 % tot 98 % van de gemeten dieptes, op halve meters. Een\n * vast bereik maakt een diep vak (C3, rond −23 m) één kleur; zo is in elk vak\n * het reliëf te zien. De legenda zegt welk bereik het is.\n */\nexport function kleurBereik(dieptes: ArrayLike<number>): { min: number; max: number } | null {\n  const geldig: number[] = [];\n  for (let i = 0; i < dieptes.length; i++) {\n    if (Number.isFinite(dieptes[i])) geldig.push(dieptes[i]);\n  }\n  if (geldig.length === 0) return null;\n  geldig.sort((p, q) => p - q);\n  const kwantiel = (q: number) =>\n    geldig[Math.min(geldig.length - 1, Math.floor(q * geldig.length))];\n  let min = Math.floor(kwantiel(0.02) * 2) / 2;\n  let max = Math.ceil(kwantiel(0.98) * 2) / 2;\n  if (max - min < 1) {\n    const midden = (min + max) / 2;\n    min = midden - 0.5;\n    max = midden + 0.5;\n  }\n  return { min, max };\n}\n\n/** Kleur van één diepte binnen het bereik; ondieper dan `max` krijgt de bovenste kleur. */\nexport function meetKleur(diepte: number, bereik: { min: number; max: number }) {\n  return depthToColor(Math.min(diepte, bereik.max), { mode: 'absolute', ...bereik });\n}\n","import { useEffect, useRef, useState } from 'react';\n\nimport { getDisplayCogUrl } from '@/js/hooks/use-datasets';\nimport { leesVenster } from '@/js/lib/bathycog-delta';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { wgs84ToRd } from '@/js/lib/projections';\nimport {\n  type Ring,\n  binnen,\n  kleurBereik,\n  meetKleur,\n  mercatorKader,\n  pixelNaarMercator,\n  vakKader,\n} from '@/js/lib/vak-meting';\n\n/**\n * De ruwe meting van één peiling binnen het vak: elke pixel is de gemeten\n * diepte, gedraaid zodat de lange as van het vak horizontaal ligt. Vervangt\n * de heatmap, die met 30 × 12 cellen hetzelfde vak grover liet zien.\n */\n\n/** Breedte van het canvas in pixels; het schaalt mee met de kaart. */\nconst BREEDTE = 1400;\nconst MIN_HOOGTE = 180;\nconst MAX_HOOGTE = 520;\nconst MARGE = 6;\n/** Bovengrens voor het rastervenster dat uit de COG gelezen wordt. */\nconst MAX_VENSTER = 2400;\n\ninterface VakMetingProps {\n  datasetId: number;\n  surveyDate: string | null | undefined;\n  /** Vakgrens in WGS84 (zoals de kaart hem krijgt). */\n  polygon: GeoJSON.Polygon | GeoJSON.MultiPolygon;\n  /** OBB-hoek in graden (RD), lange as → +x. */\n  angleDeg: number;\n  ngd: number | null | undefined;\n}\n\ntype Stand =\n  | { soort: 'laden' }\n  | { soort: 'leeg' }\n  | { soort: 'fout' }\n  | { soort: 'klaar'; bereik: { min: number; max: number } };\n\nfunction ringenRd(geom: GeoJSON.Polygon | GeoJSON.MultiPolygon): Ring[] {\n  const ringen = geom.type === 'Polygon' ? geom.coordinates : geom.coordinates.flat();\n  return ringen.map((r) => r.map(([lng, lat]) => wgs84ToRd(lng, lat)));\n}\n\nexport function VakMeting({ datasetId, surveyDate, polygon, angleDeg, ngd }: VakMetingProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const [stand, setStand] = useState<Stand>({ soort: 'laden' });\n\n  useEffect(() => {\n    let weg = false;\n    setStand({ soort: 'laden' });\n    (async () => {\n      const url = await getDisplayCogUrl(datasetId);\n      if (weg) return;\n      if (!url) {\n        setStand({ soort: 'leeg' });\n        return;\n      }\n      const kader = vakKader(ringenRd(polygon), angleDeg);\n      const verhouding = kader.hoogte / kader.breedte;\n      const hoogte = Math.round(\n        Math.min(MAX_HOOGTE, Math.max(MIN_HOOGTE, BREEDTE * verhouding + 2 * MARGE))\n      );\n      const schaal = Math.min(\n        (BREEDTE - 2 * MARGE) / kader.breedte,\n        (hoogte - 2 * MARGE) / kader.hoogte\n      );\n      const afb = pixelNaarMercator(kader, BREEDTE, hoogte, schaal);\n      const [x0, y0, x1, y1] = mercatorKader(afb, BREEDTE, hoogte);\n      // Eén rasterpixel per canvaspixel is genoeg; de COG heeft ~1 m.\n      const pixelM = Math.hypot(afb.dxi, afb.dyi);\n      const vb = Math.min(MAX_VENSTER, Math.ceil((x1 - x0) / pixelM));\n      const vh = Math.min(MAX_VENSTER, Math.ceil((y1 - y0) / pixelM));\n      const venster = await leesVenster(url, [x0, y0, x1, y1], vb, vh);\n      if (weg) return;\n\n      // Per canvaspixel de diepte, alleen binnen de vakgrens.\n      const dieptes = new Float32Array(BREEDTE * hoogte).fill(NaN);\n      for (let j = 0; j < hoogte; j++) {\n        const ly = (hoogte / 2 - j - 0.5) / schaal;\n        for (let i = 0; i < BREEDTE; i++) {\n          const lx = (i + 0.5 - BREEDTE / 2) / schaal;\n          if (!binnen(kader.ringen, lx, ly)) continue;\n          const mx = afb.x0 + i * afb.dxi + j * afb.dxj;\n          const my = afb.y0 + i * afb.dyi + j * afb.dyj;\n          const kol = Math.floor(((mx - x0) / (x1 - x0)) * vb);\n          const rij = Math.floor(((y1 - my) / (y1 - y0)) * vh);\n          if (kol < 0 || kol >= vb || rij < 0 || rij >= vh) continue;\n          dieptes[j * BREEDTE + i] = venster[rij * vb + kol];\n        }\n      }\n      const bereik = kleurBereik(dieptes);\n      const canvas = canvasRef.current;\n      if (!bereik || !canvas) {\n        setStand({ soort: 'leeg' });\n        return;\n      }\n\n      canvas.width = BREEDTE;\n      canvas.height = hoogte;\n      const ctx = canvas.getContext('2d');\n      if (!ctx) return;\n      const beeld = ctx.createImageData(BREEDTE, hoogte);\n      for (let p = 0; p < dieptes.length; p++) {\n        const d = dieptes[p];\n        if (!Number.isFinite(d)) continue;\n        const [r, g, b] = meetKleur(d, bereik);\n        beeld.data[4 * p] = r;\n        beeld.data[4 * p + 1] = g;\n        beeld.data[4 * p + 2] = b;\n        beeld.data[4 * p + 3] = 255;\n      }\n      ctx.putImageData(beeld, 0, 0);\n      // De vakgrens, zodat ongemeten stukken als gat zichtbaar zijn.\n      ctx.strokeStyle = '#475569';\n      ctx.lineWidth = 1;\n      for (const ring of kader.ringen) {\n        ctx.beginPath();\n        ring.forEach(([lx, ly], n) => {\n          const px = BREEDTE / 2 + lx * schaal;\n          const py = hoogte / 2 - ly * schaal;\n          if (n === 0) ctx.moveTo(px, py);\n          else ctx.lineTo(px, py);\n        });\n        ctx.closePath();\n        ctx.stroke();\n      }\n      setStand({ soort: 'klaar', bereik });\n    })().catch(() => {\n      if (!weg) setStand({ soort: 'fout' });\n    });\n    return () => {\n      weg = true;\n    };\n  }, [datasetId, polygon, angleDeg]);\n\n  const datum = surveyDate ? new Date(`${surveyDate}T00:00:00`).toLocaleDateString('nl-NL') : null;\n\n  return (\n    <div className=\"space-y-2\">\n      <div className=\"relative\">\n        <canvas\n          ref={canvasRef}\n          className={stand.soort === 'klaar' ? 'h-auto w-full' : 'hidden'}\n          role=\"img\"\n          aria-label={`Gemeten diepte in het vak${datum ? ` op ${datum}` : ''}`}\n        />\n        {stand.soort !== 'klaar' && (\n          <div className=\"flex h-40 items-center justify-center text-sm text-muted-foreground\">\n            {stand.soort === 'laden' && 'Meting laden…'}\n            {stand.soort === 'leeg' && 'Geen meting voor dit vak.'}\n            {stand.soort === 'fout' && 'De meting kon niet geladen worden.'}\n          </div>\n        )}\n      </div>\n      {stand.soort === 'klaar' && <Legenda bereik={stand.bereik} ngd={ngd} datum={datum} />}\n    </div>\n  );\n}\n\nfunction Legenda({\n  bereik,\n  ngd,\n  datum,\n}: {\n  bereik: { min: number; max: number };\n  ngd: number | null | undefined;\n  datum: string | null;\n}) {\n  const stappen = 8;\n  const verloop = Array.from({ length: stappen }, (_, n) => {\n    const d = bereik.min + (n / (stappen - 1)) * (bereik.max - bereik.min);\n    const [r, g, b] = meetKleur(d, bereik);\n    return `rgb(${r},${g},${b}) ${Math.round((100 * n) / (stappen - 1))}%`;\n  }).join(', ');\n  const ngdPositie =\n    ngd != null && ngd > bereik.min && ngd < bereik.max\n      ? (100 * (ngd - bereik.min)) / (bereik.max - bereik.min)\n      : null;\n  return (\n    <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground\">\n      <span>Gemeten diepte{datum ? ` op ${datum}` : ''}, in m NAP</span>\n      <span className=\"inline-flex items-center gap-2\">\n        <span className=\"getal\">{getal(bereik.min, 1)}</span>\n        <span\n          className=\"relative inline-block h-3 w-48 rounded-sm\"\n          style={{ background: `linear-gradient(to right, ${verloop})` }}\n        >\n          {ngdPositie != null && (\n            <span\n              className=\"absolute -top-1 h-5 w-px bg-foreground\"\n              style={{ left: `${ngdPositie}%` }}\n              title={`NGD ${getal(ngd!, 1)}`}\n            />\n          )}\n        </span>\n        <span className=\"getal\">{getal(bereik.max, 1)}</span>\n      </span>\n      {ngdPositie != null && <span>streep: NGD {getal(ngd!, 1)}</span>}\n    </div>\n  );\n}\n","import type { LabelWerkgebied, PolygoonWerkgebied, WerkgebiedResponse } from '@/js/api/types.gen';\n\nimport { useWerkgebied } from '@/js/hooks/use-werkgebied';\n\nconst KLEUR = '#0f766e';\nconst RANDKLEUR = '#5eead4';\nconst BUITENKLEUR = '#a8a29e';\n\ntype Rij = { naam: string; binnen: number; rand: number; buiten?: boolean };\n\nfunction procent(deel: number, totaal: number): string {\n  if (totaal <= 0) return '0 %';\n  return `${Math.round((100 * deel) / totaal)} %`;\n}\n\nfunction m3(waarde: number): string {\n  return `${Math.round(waarde).toLocaleString('nl-NL')} m³`;\n}\n\nfunction datum(iso: string): string {\n  return new Date(iso).toLocaleDateString('nl-NL', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric',\n  });\n}\n\n/** De dag vóór een exclusieve einddatum: het venster loopt tot en met die dag. */\nfunction laatsteDag(iso: string): string {\n  return datum(new Date(new Date(iso).getTime() - 24 * 3600 * 1000).toISOString());\n}\n\n/** Bestemmingen van het werk met dit label, in volgorde van grootte. */\nexport function bestemmingRijen(label: LabelWerkgebied): Rij[] {\n  return label.bestemmingen.map((b) => ({\n    naam: b.vak ?? 'buiten de vakken',\n    binnen: b.binnen_m3,\n    rand: b.rand_m3,\n    buiten: b.vak === null,\n  }));\n}\n\n/** Labels van het werk dat in deze polygoon lag. */\nexport function herkomstRijen(polygoon: PolygoonWerkgebied): Rij[] {\n  return polygoon.herkomst.map((h) => ({ naam: h.label, binnen: h.volume_m3, rand: 0 }));\n}\n\n/**\n * Eén zin die zegt waar het werk met dit label lag. Het getal waar het om\n * draait — hoeveel er in het eigen vak lag — staat voorop.\n */\nexport function samenvatting(vak: string, label: LabelWerkgebied): string {\n  const totaal = label.bestemmingen.reduce((s, b) => s + b.binnen_m3 + b.rand_m3, 0);\n  const eigen = label.bestemmingen.find((b) => b.vak === vak);\n  const buiten = label.bestemmingen.find((b) => b.vak === null);\n  const elders = totaal - (eigen ? eigen.binnen_m3 + eigen.rand_m3 : 0) - (buiten?.binnen_m3 ?? 0);\n  const delen = [\n    `${procent(eigen?.binnen_m3 ?? 0, totaal)} in ${vak}`,\n    ...(eigen && eigen.rand_m3 > 0 ? [`${procent(eigen.rand_m3, totaal)} op de rand ervan`] : []),\n    `${procent(elders, totaal)} in of bij andere vakken`,\n    ...(buiten ? [`${procent(buiten.binnen_m3, totaal)} buiten de vakken`] : []),\n  ];\n  const laatste = delen.pop();\n  return `Van het werk met label ${vak} lag ${delen.join(', ')} en ${laatste}.`;\n}\n\n/**\n * Waar het baggerwerk volgens de AIS feitelijk lag (WP6 versie 1).\n *\n * De reizendatabase noemt per reis een vak; de AIS laat zien waar het schip\n * was terwijl het baggerde. Die twee vallen niet samen, en dat raakt elke\n * grafiek per vak. Daarom staan beide richtingen hier: waar het werk met dit\n * label heen ging, en welk werk er in deze polygoon lag.\n */\nexport function VakWerkgebied({ code }: { code: string }) {\n  const { data, bezig, fout } = useWerkgebied(code);\n\n  if (fout) return <Melding>Het werkgebied is niet op te halen.</Melding>;\n  if (bezig && !data) return <Melding>Bezig met ophalen…</Melding>;\n  if (!data) return null;\n  if (!data.dekking) {\n    return (\n      <Melding>\n        Er is nog geen AIS-aanlevering. Zonder vaartracks is niet te zien waar er gebaggerd is.\n      </Melding>\n    );\n  }\n  return <Werkgebied data={data} />;\n}\n\nfunction Werkgebied({ data }: { data: WerkgebiedResponse }) {\n  const periode =\n    data.van && data.tot ? `${datum(data.van)} tot en met ${laatsteDag(data.tot)}` : '';\n  const { label, polygoon } = data;\n\n  if (!label?.bestemmingen.length && !polygoon) {\n    return (\n      <Melding>\n        Van {periode} is er volgens de AIS niet gebaggerd onder label {data.vak}, en lag er geen\n        baggerwerk in deze polygoon.\n      </Melding>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4 text-sm\">\n      {label && label.bestemmingen.length > 0 && (\n        <section className=\"space-y-2\">\n          <h4 className=\"text-xs font-medium\">Werk met label {data.vak}</h4>\n          <p className=\"text-xs text-muted-foreground\">\n            {samenvatting(data.vak, label)} Over {periode}: {label.reizen_met_track} van de{' '}\n            {label.reizen} reizen hebben een AIS-track ({m3(label.volume_met_track_m3)}).\n          </p>\n          <Verdeling rijen={bestemmingRijen(label)} />\n        </section>\n      )}\n      {polygoon && (\n        <section className=\"space-y-2\">\n          <h4 className=\"text-xs font-medium\">Werk in de polygoon {data.vak}</h4>\n          <p className=\"text-xs text-muted-foreground\">\n            In deze polygoon lag {m3(polygoon.volume_m3)} baggerwerk. Daarvan stond{' '}\n            {procent(\n              polygoon.herkomst\n                .filter((h) => h.label !== data.vak)\n                .reduce((s, h) => s + h.volume_m3, 0),\n              polygoon.volume_m3\n            )}{' '}\n            onder een ander label.\n          </p>\n          <Verdeling rijen={herkomstRijen(polygoon)} />\n        </section>\n      )}\n      <p className=\"text-xs text-muted-foreground\">\n        Het volume van elke reis is verdeeld naar het aandeel van haar baggerposities per polygoon\n        (in-situ, anders beun). &lsquo;Rand&rsquo;: buiten de polygoon maar binnen{' '}\n        {Math.round(data.rand_m)} m ervan. De AIS geeft de positie van de antenne, niet van de\n        zuigkop; dat scheelt tot zo&rsquo;n honderd meter.\n      </p>\n    </div>\n  );\n}\n\nfunction Verdeling({ rijen }: { rijen: Rij[] }) {\n  const totaal = rijen.reduce((s, r) => s + r.binnen + r.rand, 0);\n  return (\n    <ul className=\"space-y-1\">\n      {rijen.map((rij) => (\n        <li key={rij.naam} className=\"grid grid-cols-[7rem_1fr_3rem] items-center gap-2 text-xs\">\n          <span className=\"truncate\">{rij.naam}</span>\n          <span className=\"flex h-3 overflow-hidden rounded-sm bg-muted\">\n            <span\n              style={{\n                width: `${(100 * rij.binnen) / Math.max(totaal, 1)}%`,\n                background: rij.buiten ? BUITENKLEUR : KLEUR,\n              }}\n            />\n            <span\n              title=\"Op de rand\"\n              style={{ width: `${(100 * rij.rand) / Math.max(totaal, 1)}%`, background: RANDKLEUR }}\n            />\n          </span>\n          <span className=\"text-right tabular-nums\">{procent(rij.binnen + rij.rand, totaal)}</span>\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nfunction Melding({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"flex min-h-24 items-center justify-center px-6 text-center text-sm text-muted-foreground\">\n      {children}\n    </div>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { WerkgebiedResponse } from '@/js/api/types.gen';\n\nimport { apiAisTracksWerkgebiedRetrieve } from '@/js/api/sdk.gen';\n\n/**\n * Waar het baggerwerk van één vak volgens de AIS lag (WP6 versie 1).\n *\n * De server rekent het voor alle vakken tegelijk uit en bewaart dat; per vak\n * ophalen is dus goedkoop na de eerste keer. Zelfde opzet als de andere\n * vakpaneel-hooks: geen eigen cache, afbreken bij een vakwissel.\n */\nexport function useWerkgebied(code: string | null) {\n  const [data, setData] = useState<WerkgebiedResponse | null>(null);\n  const [bezig, setBezig] = useState(false);\n  const [fout, setFout] = useState(false);\n\n  useEffect(() => {\n    setData(null);\n    setFout(false);\n    if (!code) {\n      setBezig(false);\n      return;\n    }\n    let afgebroken = false;\n    setBezig(true);\n\n    apiAisTracksWerkgebiedRetrieve({ query: { vak: code } })\n      .then(({ data: antwoord }) => {\n        if (!afgebroken) setData(antwoord ?? null);\n      })\n      .catch(() => {\n        if (!afgebroken) setFout(true);\n      })\n      .finally(() => {\n        if (!afgebroken) setBezig(false);\n      });\n\n    return () => {\n      afgebroken = true;\n    };\n  }, [code]);\n\n  return { data, bezig, fout };\n}\n","import * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Card({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card\"\n      className={cn(\n        'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn('leading-none font-semibold', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return <div data-slot=\"card-content\" className={cn('px-6', className)} {...props} />;\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn('flex items-center px-6 [.border-t]:pt-6', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };\n","/**\n * Drempels voor de status-kleur in de overzicht-+-detail-sectie van /app/analyse.\n *\n * Vorm: een paar (amber-grens, rood-grens). Onder de eerste grens = groen,\n * tussen de twee = amber, op of boven de tweede = rood.\n *\n * Aanpassen? Een edit hier raakt master-tabel cellen + detail-KPI-cards.\n * Niet aan tornen zonder afstemming met domein-expert (Jan / Wouter); deze\n * drempels komen uit de meeting-notitie 2026-05-22 en de bestaande Excel.\n */\n\nexport type ThresholdPair = readonly [amber: number, red: number];\n\nexport const THRESHOLDS = {\n  volume_ngd: [100, 1000] as const,\n  /** Volume boven de maximale diepte (OHD). */\n  volume_ohd: [0, 200] as const,\n  /** Ondiepste meting in cm boven NGD. <0 = nog onder NGD. */\n  cm_above_ngd: [0, 10] as const,\n} as const satisfies Record<string, ThresholdPair>;\n\nexport const STATUS_LEVELS = ['green', 'amber', 'red'] as const;\n\nexport type StatusLevel = (typeof STATUS_LEVELS)[number];\n\nexport function statusColor(\n  value: number | null | undefined,\n  thresholds: ThresholdPair\n): StatusLevel {\n  if (value == null) return 'green';\n  const [amber, red] = thresholds;\n  if (value >= red) return 'red';\n  if (value >= amber) return 'amber';\n  return 'green';\n}\n\n/**\n * Tailwind class-fragmenten per status. Gebruik bv. als\n *   `cn(STATUS_CELL_CLASSES[color], 'p-2 text-right')`.\n */\nexport const STATUS_CELL_CLASSES: Record<StatusLevel, string> = {\n  green: 'bg-emerald-50 text-emerald-900',\n  amber: 'bg-amber-50 text-amber-900',\n  red: 'bg-rose-50 text-rose-900',\n} as const;\n","import { MapPin, Ruler } from 'lucide-react';\nimport { Link } from 'react-router';\n\nimport { VakBaggerhoeveelheden } from './VakBaggerhoeveelheden';\nimport { VakCrossSection, type CrossSectionSample } from './VakCrossSection';\nimport { VakMeting } from './VakMeting';\nimport { VeranderingTovVorige } from './VakOverviewTable';\nimport { VakWerkgebied } from './VakWerkgebied';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\nimport type { ReactNode } from 'react';\n\nimport { Button } from '@/js/components/ui/button';\nimport { Card, CardContent, CardHeader, CardTitle } from '@/js/components/ui/card';\nimport { STATUS_CELL_CLASSES, THRESHOLDS, statusColor } from '@/js/lib/analyse-thresholds';\nimport { cn } from '@/js/lib/utils';\n\ninterface BaggervakLevels {\n  ngd: number | null;\n  ohd: number | null;\n}\n\ninterface VakDetailPanelProps {\n  code: string;\n  polygon: GeoJSON.Polygon | GeoJSON.MultiPolygon;\n  angleDeg: number;\n  levels: BaggervakLevels;\n  /** Laatste IN-peiling. Null als geen IN-peiling beschikbaar. */\n  latestDataset: SurveyDataset | null;\n  /** Diepte- en kuberingsgrafieken; staan onder de verdeling en het profiel. */\n  trends?: ReactNode;\n  /** Peilingtabel; komt na het baggerwerk, als naslag. */\n  peilingen?: ReactNode;\n}\n\nexport function VakDetailPanel({\n  code,\n  polygon,\n  angleDeg,\n  levels,\n  latestDataset,\n  trends,\n  peilingen,\n}: VakDetailPanelProps) {\n  const ds = latestDataset;\n\n  if (!ds) {\n    return (\n      <Card>\n        <CardHeader>\n          <CardTitle>Vak {code}</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <p className=\"text-sm text-muted-foreground\">\n            Nog geen peilingen beschikbaar voor dit vak.\n          </p>\n        </CardContent>\n      </Card>\n    );\n  }\n\n  return (\n    <section className=\"min-w-0 space-y-3 rounded-xl border bg-card p-5\">\n      {/* 1. Header */}\n      <h2 className=\"text-lg font-semibold\">Vak {code}</h2>\n      <div className=\"text-sm text-muted-foreground\">\n        <span className=\"font-medium text-foreground\">Laatste periodieke peiling</span>\n        {ds.survey_date && (\n          <>\n            {' · peiling '}\n            {new Date(`${ds.survey_date}T00:00:00`).toLocaleDateString('nl-NL')}\n          </>\n        )}\n        {levels.ngd != null && ` · NGD ${levels.ngd.toFixed(1)}`}\n        {levels.ohd != null && ` · max. ${levels.ohd.toFixed(1)}`}\n      </div>\n\n      {/* 2. Status-KPI's */}\n      <div className=\"grid grid-cols-2 gap-2\">\n        <KpiCard\n          label=\"m³ boven NGD\"\n          value={ds.volume_above_ngd_m3}\n          delta={ds.delta_volume_above_ngd_m3}\n          vorigeDatum={ds.vorige_peiling_datum}\n          sub={ds.cm_above_ngd != null ? `ondiepte ${ds.cm_above_ngd.toFixed(1)} cm` : undefined}\n          color={statusColor(ds.volume_above_ngd_m3 ?? null, THRESHOLDS.volume_ngd)}\n        />\n        <KpiCard\n          label=\"m³ boven max.\"\n          value={ds.volume_above_ohd_m3}\n          delta={ds.delta_volume_above_ohd_m3}\n          vorigeDatum={ds.vorige_peiling_datum}\n          color={statusColor(ds.volume_above_ohd_m3 ?? null, THRESHOLDS.volume_ohd)}\n        />\n      </div>\n\n      {/* 3. De meting zelf, groot, en het dwarsprofiel eronder */}\n      <Card>\n        <CardHeader>\n          <CardTitle className=\"text-sm\">Meting</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <VakMeting\n            datasetId={ds.id}\n            surveyDate={ds.survey_date}\n            polygon={polygon}\n            angleDeg={angleDeg}\n            ngd={levels.ngd}\n          />\n        </CardContent>\n      </Card>\n      <Card>\n        <CardHeader>\n          <CardTitle className=\"text-sm\">Dwarsprofiel + Panamax</CardTitle>\n        </CardHeader>\n        <CardContent>\n          <VakCrossSection\n            samples={(ds.cross_section_samples as CrossSectionSample[] | null) ?? []}\n            referenceLevels={levels}\n            width={900}\n            height={240}\n          />\n        </CardContent>\n      </Card>\n\n      {/* 4. Diepte en kubering over tijd */}\n      {trends}\n\n      {/* 5. Baggeractiviteit — staat onder de volumelijn omdat de twee elkaar\n          verklaren: daalt het volume boven NGD doordat er gebaggerd is, of\n          vanzelf. Zie #119. Ernaast waar het werk volgens de AIS lag (WP6). */}\n      <div className=\"grid grid-cols-1 gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]\">\n        <Card>\n          <CardHeader>\n            <CardTitle className=\"text-sm\">Gebaggerde hoeveelheid</CardTitle>\n          </CardHeader>\n          <CardContent>\n            <VakBaggerhoeveelheden code={code} />\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader>\n            <CardTitle className=\"text-sm\">Waar is gebaggerd? (AIS)</CardTitle>\n          </CardHeader>\n          <CardContent>\n            <VakWerkgebied code={code} />\n          </CardContent>\n        </Card>\n      </div>\n\n      {/* 6. Alle peilingen */}\n      {peilingen}\n\n      {/* 7. Knoppen */}\n      <div className=\"flex gap-2\">\n        <Button asChild variant=\"outline\" size=\"sm\">\n          <Link to={`/app/kaart?vak=${code}`}>\n            <MapPin className=\"mr-2 h-4 w-4\" />\n            Bekijk op kaart\n          </Link>\n        </Button>\n        <Button asChild variant=\"outline\" size=\"sm\">\n          <Link to={`/app/kaart?vak=${code}&tool=cross-section`}>\n            <Ruler className=\"mr-2 h-4 w-4\" />\n            Open dwarsprofiel-tool\n          </Link>\n        </Button>\n      </div>\n    </section>\n  );\n}\n\nfunction KpiCard({\n  label,\n  value,\n  delta,\n  vorigeDatum,\n  sub,\n  color,\n}: {\n  label: string;\n  value: number | null | undefined;\n  /** Verandering sinds de vorige peiling, klein onder het getal. */\n  delta?: number | null;\n  vorigeDatum?: string | null;\n  sub?: string;\n  color: 'green' | 'amber' | 'red';\n}) {\n  return (\n    <div className={cn('rounded-md border p-3', STATUS_CELL_CLASSES[color])}>\n      <div className=\"text-xs uppercase opacity-80\">{label}</div>\n      <div className=\"text-2xl font-semibold tabular-nums\">\n        {value == null ? '—' : Math.round(value).toLocaleString('nl-NL')}\n      </div>\n      <VeranderingTovVorige\n        delta={delta}\n        vorigeDatum={vorigeDatum}\n        className=\"text-current opacity-80\"\n      />\n      {sub && <div className=\"text-xs opacity-80\">{sub}</div>}\n    </div>\n  );\n}\n","import { cmAfgerond, isPeilingVerouderd } from './VakOverviewTable';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\nimport { getal } from '@/js/lib/getal-notatie';\nimport { cn } from '@/js/lib/utils';\n\n/** Eén vak in de lijn: het ondiepste punt t.o.v. NGD uit de laatste periodieke peiling. */\nexport interface LijnVak {\n  code: string;\n  /** Dezelfde waarde als de kolom cm boven NGD in de tabel; null zonder peiling. */\n  cm: number | null;\n  peildatum: string | null;\n}\n\n/**\n * IL ligt naast de geul, ter hoogte van C2–C3. In de lijn zou het lijken alsof\n * een schip er doorheen vaart; daarom staat het er los onder, op zijn plek.\n */\nconst NAAST_DE_GEUL = { code: 'IL', vanaf: 'C2', naast: 'C2–C3' } as const;\n\n/**\n * De vakken van west naar Eemshaven, in `Baggervak.order` (dezelfde volgorde\n * als de backend ze sorteert), met IL apart. De API geeft alleen vakken met\n * geometrie, dus peilinghaakjes als P0 en KP15 vallen er al uit.\n */\nexport function maakVakkenLijn(\n  features: ReadonlyArray<GeoJSON.Feature>,\n  latestByVak: ReadonlyMap<string, SurveyDataset>\n): { lijn: LijnVak[]; los: LijnVak | null } {\n  const vakken = features\n    .filter((f) => typeof f.properties?.code === 'string')\n    .map((f) => ({\n      code: f.properties!.code as string,\n      order: (f.properties!.order as number | undefined) ?? 0,\n    }))\n    .sort((a, b) => a.order - b.order || a.code.localeCompare(b.code))\n    .map(({ code }) => {\n      const ds = latestByVak.get(code);\n      return { code, cm: ds?.cm_above_ngd ?? null, peildatum: ds?.survey_date ?? null };\n    });\n  return {\n    lijn: vakken.filter((v) => v.code !== NAAST_DE_GEUL.code),\n    los: vakken.find((v) => v.code === NAAST_DE_GEUL.code) ?? null,\n  };\n}\n\nconst VEROUDERD_VLAK = 'border border-dashed border-[#9AA0A8] bg-muted';\n\n/**\n * Tailwind-klasse voor het vlak van een vak; dezelfde grenzen als de legenda.\n * Een verouderde peiling wordt grijs, net als de badge in de tabel: op een\n * oude peiling stuur je niet, dus het vak mag ook niet om aandacht vragen.\n */\nexport function lijnKleur(cm: number | null, verouderd = false): string {\n  if (cm == null) return 'bg-muted';\n  if (verouderd) return VEROUDERD_VLAK;\n  if (cmAfgerond(cm) > 50) return 'bg-destructive';\n  if (cmAfgerond(cm) > 0) return 'bg-[#F2B8B0]';\n  return 'bg-[#D5DCE3]';\n}\n\nfunction lijnTekstKleur(cm: number | null, verouderd: boolean): string {\n  if (cm == null || verouderd) return 'text-muted-foreground';\n  if (cmAfgerond(cm) > 50) return 'text-destructive';\n  if (cmAfgerond(cm) > 0) return 'text-leisteen';\n  return 'text-muted-foreground';\n}\n\nconst LEGENDA = [\n  ['bg-destructive', 'meer dan 50 cm boven'],\n  ['bg-[#F2B8B0]', 'tot 50 cm boven'],\n  ['bg-[#D5DCE3]', 'op diepte'],\n  [VEROUDERD_VLAK, 'peiling verouderd'],\n] as const;\n\nfunction cmTekst(cm: number | null): string {\n  return cm == null ? '—' : getal(cm, 1, { plusteken: true });\n}\n\nfunction maandJaar(datum: string): string {\n  return new Date(`${datum}T00:00:00`).toLocaleDateString('nl-NL', {\n    month: 'long',\n    year: 'numeric',\n  });\n}\n\nfunction isVerouderd(v: LijnVak): boolean {\n  return v.peildatum != null && isPeilingVerouderd(v.peildatum);\n}\n\n/** Voor een schermlezer: zonder peiling geen streepje, dat klinkt als een waarde. */\nfunction waardeLabel(v: LijnVak): string {\n  if (v.cm == null) return 'geen peiling';\n  const waarde = `${cmTekst(v.cm)} cm t.o.v. NGD`;\n  return isVerouderd(v) ? `${waarde}, peiling ${maandJaar(v.peildatum!)}` : waarde;\n}\n\ninterface VakkenLijnProps {\n  lijn: ReadonlyArray<LijnVak>;\n  los: LijnVak | null;\n  selectedCode: string | null;\n  onSelect: (code: string) => void;\n}\n\n/**\n * Alle vakken in één oogopslag, zoals ze in het water liggen. De tabel eronder\n * sorteert op kuubs; hier zie je waar langs de geul het ondiep is.\n */\nexport function VakkenLijn({ lijn, los, selectedCode, onSelect }: VakkenLijnProps) {\n  const kolommen = { gridTemplateColumns: `repeat(${lijn.length}, minmax(0, 1fr))` };\n  // Begint één vak vóór C2, zoals in het ontwerp: zo staat \"naast C2–C3\" er\n  // ongeveer onder. Het kader groeit met de tekst mee; drie vaste kolommen\n  // waren op 1366 breed te smal voor \"peiling april 2025\".\n  const naastIndex = lijn.findIndex((v) => v.code === NAAST_DE_GEUL.vanaf);\n  const losStart = Math.max(1, Math.min(naastIndex, lijn.length - 2));\n  const losGekozen = los != null && los.code === selectedCode;\n  const losVerouderd = los != null && isVerouderd(los);\n\n  return (\n    <section\n      aria-label=\"Vakken van west naar Eemshaven\"\n      className=\"flex flex-col gap-3.5 rounded-xl border bg-card p-5\"\n    >\n      <div className=\"flex flex-wrap items-baseline gap-4\">\n        <h2 className=\"text-lg font-semibold\">Ondiepste punt t.o.v. NGD, per vak</h2>\n        <ul className=\"ml-auto flex flex-wrap items-center gap-4 text-xs text-muted-foreground\">\n          {LEGENDA.map(([kleur, label]) => (\n            <li key={label} className=\"flex items-center gap-1.5\">\n              <span aria-hidden className={cn('h-2.5 w-3.5 rounded-xs', kleur)} />\n              {label}\n            </li>\n          ))}\n        </ul>\n      </div>\n\n      {/* Op een smal scherm schuift de lijn; zestien vakken in een telefoonbreedte\n          zijn niet meer te lezen. */}\n      <div className=\"overflow-x-auto\">\n        <div className=\"flex min-w-[900px] flex-col gap-2\">\n          <div className=\"flex items-start gap-1.5\">\n            <span className=\"w-9 shrink-0 pt-6 text-xs text-muted-foreground\">west</span>\n            <div className=\"grid min-w-0 flex-1 gap-[3px]\" style={kolommen}>\n              {lijn.map((v) => {\n                const gekozen = v.code === selectedCode;\n                const verouderd = isVerouderd(v);\n                return (\n                  <button\n                    key={v.code}\n                    type=\"button\"\n                    aria-pressed={gekozen}\n                    aria-label={`${v.code}, ${waardeLabel(v)}`}\n                    onClick={() => onSelect(v.code)}\n                    className=\"group flex min-w-0 cursor-pointer flex-col items-center gap-1.5\"\n                  >\n                    <span\n                      className={cn(\n                        // Plex Mono is alleen in 400 en 500 geladen: de kleur\n                        // draagt de keuze, niet een nagemaakte vette letter.\n                        'font-mono text-xs font-medium',\n                        gekozen ? 'text-primary' : 'text-leisteen'\n                      )}\n                    >\n                      {v.code}\n                    </span>\n                    <span\n                      data-testid={`vakkenlijn-vlak-${v.code}`}\n                      className={cn(\n                        'h-5 w-full rounded-[3px] outline-offset-1 group-hover:outline-2 group-hover:outline-primary/40',\n                        lijnKleur(v.cm, verouderd),\n                        gekozen && 'ring-2 ring-primary ring-offset-2 ring-offset-card'\n                      )}\n                    />\n                    <span\n                      className={cn(\n                        'getal text-xs whitespace-nowrap',\n                        lijnTekstKleur(v.cm, verouderd)\n                      )}\n                    >\n                      {cmTekst(v.cm)}\n                    </span>\n                  </button>\n                );\n              })}\n            </div>\n            <span className=\"w-[72px] shrink-0 pt-6 text-right text-xs text-muted-foreground\">\n              Eemshaven\n            </span>\n          </div>\n\n          {los && (\n            <div className=\"flex gap-1.5\">\n              <span className=\"w-9 shrink-0\" />\n              <div className=\"grid min-w-0 flex-1 gap-[3px]\" style={kolommen}>\n                {/* Zonder getal, zoals in het ontwerp: het kader zegt waar IL ligt\n                    en hoe oud de peiling is; de cm staat in de tabel. */}\n                <button\n                  type=\"button\"\n                  aria-pressed={losGekozen}\n                  aria-label={`${los.code}, naast de geul bij ${NAAST_DE_GEUL.naast}, ${waardeLabel(los)}`}\n                  onClick={() => onSelect(los.code)}\n                  style={{ gridColumn: `${losStart} / -1` }}\n                  className={cn(\n                    'flex max-w-full min-w-0 cursor-pointer items-center gap-2 justify-self-start rounded-[4px] border border-dashed border-[#9AA0A8] px-2 py-1.5 text-xs whitespace-nowrap text-muted-foreground hover:bg-muted',\n                    losGekozen\n                      ? 'bg-gekozen ring-2 ring-primary ring-offset-2 ring-offset-card'\n                      : 'bg-card'\n                  )}\n                >\n                  <span className=\"font-mono font-medium text-leisteen\">{los.code}</span>\n                  <span className=\"truncate\">\n                    naast {NAAST_DE_GEUL.naast}\n                    {losVerouderd && ` · peiling ${maandJaar(los.peildatum!)}`}\n                  </span>\n                </button>\n              </div>\n              <span className=\"w-[72px] shrink-0\" />\n            </div>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n}\n","import * as React from 'react';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from '@/js/lib/utils';\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  );\n};\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>['children'];\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color);\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  }) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey || item?.dataKey || item?.name || 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? config[label as keyof typeof config]?.label || label\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>{labelFormatter(value, payload)}</div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n  if (!active || !payload?.length) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey || item.name || item.dataKey || 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color || item.payload.fill || item.color;\n\n            return (\n              <div\n                key={item.dataKey}\n                className={cn(\n                  '[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',\n                  indicator === 'dot' && 'items-center'\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label || item.name}\n                        </span>\n                      </div>\n                      {item.value && (\n                        <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                          {item.value.toLocaleString()}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> &\n  Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {\n    hideIcon?: boolean;\n    nameKey?: string;\n  }) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item) => {\n          const key = `${nameKey || item.dataKey || 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              key={item.value}\n              className={cn(\n                '[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3'\n              )}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\n// Helper to extract item config from a payload.\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n  if (typeof payload !== 'object' || payload === null) {\n    return undefined;\n  }\n\n  const payloadPayload =\n    'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (key in payload && typeof payload[key as keyof typeof payload] === 'string') {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n","/**\n * Een echte tijdas voor de grafieken over tijd (Analyse).\n *\n * Een categorie-as zet elke peiling even ver uit elkaar. Met de historische\n * jaarpeilingen (2017–2022) naast de maandpeilingen vanaf 2025 lijkt\n * een jaar dan even lang als een maand, en een trend dus twaalf keer zo steil.\n * Hier staat elke peiling op zijn datum, met streepjes op de eerste van een\n * maand en een stapgrootte die past bij de lengte van de reeks.\n */\n\n/** Recharts-sleutel waaronder de grafieken de tijd in milliseconden zetten. */\nexport const TIJD_KEY = '__tijd';\n\n/** Milliseconden voor een ISO-datum ('2019-06-15'), middernacht UTC. */\nexport function tijdVanDatum(iso: string): number {\n  // Een kale datum leest JavaScript als UTC; zo hangt de as niet af van de\n  // tijdzone van de browser, en de labels hieronder rekenen ook in UTC.\n  return Date.parse(iso.slice(0, 10));\n}\n\n/** Toegestane afstanden tussen twee streepjes, in maanden. */\nconst STAPPEN_MAANDEN = [1, 2, 3, 6, 12, 24, 60];\n/** Ruimte links en rechts van de eerste en laatste peiling: een halve maand. */\nconst RAND_MS = 15 * 24 * 3600 * 1000;\n\nexport interface Tijdas {\n  domain: [number, number];\n  ticks: number[];\n  formatTick: (tijd: number) => string;\n}\n\nfunction maandIndex(tijd: number): number {\n  const d = new Date(tijd);\n  return d.getUTCFullYear() * 12 + d.getUTCMonth();\n}\n\nfunction vanMaandIndex(index: number): number {\n  return Date.UTC(Math.floor(index / 12), index % 12, 1);\n}\n\n/** \"jun '25\" of, bij streepjes van een jaar of meer, \"2025\". */\nfunction maakLabel(stapMaanden: number) {\n  return (tijd: number): string => {\n    const d = new Date(tijd);\n    if (stapMaanden >= 12) return String(d.getUTCFullYear());\n    const maand = d.toLocaleString('nl-NL', { month: 'short', timeZone: 'UTC' }).replace('.', '');\n    return `${maand} '${String(d.getUTCFullYear()).slice(2)}`;\n  };\n}\n\n/**\n * Domein, streepjes en labels voor een tijdas over deze tijden (ms).\n * Hooguit `maxStreepjes` streepjes, op de eerste van een maand; bij een stap\n * van een jaar of meer op 1 januari.\n */\nexport function tijdas(tijden: number[], maxStreepjes = 10): Tijdas {\n  const geldig = tijden.filter((t) => Number.isFinite(t));\n  if (geldig.length === 0) {\n    return { domain: [0, 1], ticks: [], formatTick: maakLabel(1) };\n  }\n  const min = Math.min(...geldig) - RAND_MS;\n  const max = Math.max(...geldig) + RAND_MS;\n  const eerste = maandIndex(min) + (vanMaandIndex(maandIndex(min)) < min ? 1 : 0);\n  const laatste = maandIndex(max);\n  const aantalMaanden = Math.max(laatste - eerste + 1, 1);\n  const stap =\n    STAPPEN_MAANDEN.find((s) => Math.ceil(aantalMaanden / s) <= maxStreepjes) ??\n    STAPPEN_MAANDEN[STAPPEN_MAANDEN.length - 1];\n\n  const ticks: number[] = [];\n  for (let i = Math.ceil(eerste / stap) * stap; i <= laatste; i += stap) {\n    ticks.push(vanMaandIndex(i));\n  }\n  return { domain: [min, max], ticks, formatTick: maakLabel(stap) };\n}\n\n/** \"14 juni 2026\", voor de tooltip. */\nexport function datumLang(iso: string): string {\n  return new Date(tijdVanDatum(iso)).toLocaleDateString('nl-NL', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric',\n    timeZone: 'UTC',\n  });\n}\n\n/** Zet bij elk record de tijd in ms onder `TIJD_KEY`, naast de datum. */\nexport function metTijd<T extends Record<string, unknown>>(\n  data: T[],\n  dateKey: string\n): (T & { [TIJD_KEY]: number })[] {\n  return data.map((r) => ({ ...r, [TIJD_KEY]: tijdVanDatum(String(r[dateKey])) }));\n}\n","import { useMemo } from 'react';\nimport { CartesianGrid, ComposedChart, Line, XAxis, YAxis } from 'recharts';\n\nimport type { TrendRecord } from '@/js/lib/trend-data';\n\nimport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  type ChartConfig,\n} from '@/js/components/ui/chart';\nimport { datumLang, metTijd, tijdas, TIJD_KEY } from '@/js/lib/tijdas';\n\n/** NGD en de maximale diepte (OHD); IVD staat niet in het bestek 2026. */\nexport type VolumeMetric = 'ngd' | 'ohd';\nexport type VolumeType = 'volume' | 'surface';\n\nexport interface VolumeLine {\n  dataKey: string;\n  color: string;\n  label: string;\n  /** Forecast line: dashed, hollow dots. */\n  dashed?: boolean;\n}\n\nexport interface VolumeTrendChartProps {\n  data: TrendRecord[];\n  dateKey: string;\n  lines: VolumeLine[];\n  /** Y-axis label — e.g. \"m³ boven niveau\". */\n  yLabel: string;\n  /** Unit suffix shown in the tooltip after each value (e.g. \"m³\"). */\n  unit: string;\n}\n\nfunction formatYTick(value: unknown): string {\n  if (typeof value !== 'number' || !Number.isFinite(value)) return '';\n  if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n  if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(0)}k`;\n  return Math.round(value).toLocaleString('nl-NL');\n}\n\nfunction formatTooltipValue(value: number | string, unit: string): string {\n  if (typeof value !== 'number' || !Number.isFinite(value)) return '—';\n  return `${Math.round(value).toLocaleString('nl-NL')} ${unit}`;\n}\n\nexport function VolumeTrendChart({ data, dateKey, lines, yLabel, unit }: VolumeTrendChartProps) {\n  const chartConfig = useMemo<ChartConfig>(() => {\n    const cfg: ChartConfig = {};\n    for (const line of lines) {\n      cfg[line.dataKey] = { label: line.label, color: line.color };\n    }\n    return cfg;\n  }, [lines]);\n\n  // Time axis: each point sits on its date, so yearly surveys (2017-2022) are\n  // not drawn as close together as monthly ones.\n  const tijdData = useMemo(() => metTijd(data, dateKey), [data, dateKey]);\n  const as = useMemo(() => tijdas(tijdData.map((r) => r[TIJD_KEY])), [tijdData]);\n\n  if (data.length === 0) {\n    return (\n      <div className=\"flex h-full items-center justify-center\">\n        <p className=\"text-sm text-muted-foreground\">Geen data beschikbaar.</p>\n      </div>\n    );\n  }\n\n  return (\n    <ChartContainer config={chartConfig} className=\"h-full w-full\">\n      <ComposedChart data={tijdData} margin={{ top: 10, right: 20, bottom: 0, left: 10 }}>\n        <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n        <XAxis\n          dataKey={TIJD_KEY}\n          type=\"number\"\n          scale=\"time\"\n          domain={as.domain}\n          ticks={as.ticks}\n          tickFormatter={as.formatTick}\n          className=\"text-xs\"\n        />\n        <YAxis\n          tickFormatter={formatYTick}\n          label={{\n            value: yLabel,\n            angle: -90,\n            position: 'insideLeft',\n            style: { textAnchor: 'middle' },\n          }}\n          className=\"text-xs\"\n          width={60}\n        />\n        <ChartTooltip\n          content={\n            <ChartTooltipContent\n              labelFormatter={(_, payload) => {\n                const datum = payload?.[0]?.payload?.[dateKey];\n                return typeof datum === 'string' ? datumLang(datum) : '';\n              }}\n              formatter={(value) => formatTooltipValue(value as number | string, unit)}\n            />\n          }\n        />\n        {lines.map((line) => (\n          <Line\n            key={line.dataKey}\n            type=\"monotone\"\n            dataKey={line.dataKey}\n            stroke={line.color}\n            strokeWidth={2}\n            strokeDasharray={line.dashed ? '6 4' : undefined}\n            dot={line.dashed ? { r: 3, fill: 'var(--background)' } : { r: 3 }}\n            activeDot={{ r: 5 }}\n            connectNulls\n          />\n        ))}\n      </ComposedChart>\n    </ChartContainer>\n  );\n}\n","// frontend/js/components/DepthTimeChart.tsx\nimport { useEffect, useMemo, useState, type WheelEvent } from 'react';\nimport {\n  CartesianGrid,\n  ComposedChart,\n  Line,\n  ReferenceArea,\n  ReferenceLine,\n  XAxis,\n  YAxis,\n} from 'recharts';\n\nimport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  type ChartConfig,\n} from '@/js/components/ui/chart';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { NAP_COLOR, REF_LEVEL_COLORS, REF_LEVEL_LABELS } from '@/js/lib/reference-levels';\nimport { datumLang, metTijd, tijdas, TIJD_KEY } from '@/js/lib/tijdas';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DepthLine {\n  dataKey: string;\n  color: string;\n  label: string;\n}\n\nexport interface DepthBand {\n  minKey: string;\n  maxKey: string;\n  /** Stroke color for the dashed min/max boundary lines. */\n  color: string;\n}\n\nexport interface DepthTimeChartProps {\n  /** Recharts-compatible records, one per date. */\n  data: Record<string, unknown>[];\n  /** Key in each record holding the ISO date string. */\n  dateKey: string;\n  /** Lines to render — one per metric/vak. */\n  lines: DepthLine[];\n  /** Optional min/max band fills (for \"Dieptebereik\" mode). */\n  bands?: DepthBand[];\n  /** Reference depth levels — renders horizontal dashed lines. */\n  referenceLevels?: { ngd?: number; ivd?: number; ohd?: number };\n  /** Show blue water gradient above NAP=0 (used by Tijdreeks, optional). */\n  showWaterFill?: boolean;\n  /** Y-axis label. Default: \"m NAP\". */\n  yLabel?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst ZOOM_FACTOR = 0.1;\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function DepthTimeChart({\n  data,\n  dateKey,\n  lines,\n  bands,\n  referenceLevels,\n  showWaterFill = false,\n  yLabel = 'm NAP',\n}: DepthTimeChartProps) {\n  // Auto Y-domain from data (includes line values, band values, and reference levels)\n  const autoYDomain = useMemo(() => {\n    // Vanuit de data, niet vanaf nul: de bodem ligt rond -15 m NAP, en een\n    // as tot boven nul drukte de lijn plat tegen de onderrand.\n    let min = Infinity;\n    let max = -Infinity;\n    const scan = (v: unknown) => {\n      if (typeof v === 'number' && Number.isFinite(v)) {\n        if (v < min) min = v;\n        if (v > max) max = v;\n      }\n    };\n    for (const record of data) {\n      for (const line of lines) scan(record[line.dataKey]);\n      if (bands) {\n        for (const band of bands) {\n          scan(record[band.minKey]);\n          scan(record[band.maxKey]);\n        }\n      }\n    }\n    if (referenceLevels) {\n      for (const v of Object.values(referenceLevels)) scan(v);\n    }\n    if (!Number.isFinite(min)) return [-1, 1] as [number, number];\n    const pad = (max - min) * 0.1 || 1;\n    return [min - pad, max + pad] as [number, number];\n  }, [data, lines, bands, referenceLevels]);\n\n  const [yDomain, setYDomain] = useState<[number, number] | null>(null);\n  const domain = yDomain ?? autoYDomain;\n\n  // Reset zoom when data changes\n  useEffect(() => setYDomain(null), [autoYDomain]);\n\n  const onWheel = (e: WheelEvent) => {\n    e.preventDefault();\n    const [yMin, yMax] = domain;\n    const range = yMax - yMin;\n    const delta = e.deltaY > 0 ? ZOOM_FACTOR : -ZOOM_FACTOR;\n    const newMin = yMin - range * delta;\n    const newMax = yMax + range * delta;\n    if (newMax - newMin > 0.5) setYDomain([newMin, newMax]);\n  };\n\n  // Time axis: each point sits on its date, so yearly surveys (2017-2022) are\n  // not drawn as close together as monthly ones.\n  const tijdData = useMemo(() => metTijd(data, dateKey), [data, dateKey]);\n  const as = useMemo(() => tijdas(tijdData.map((r) => r[TIJD_KEY])), [tijdData]);\n\n  // Chart config for tooltips\n  const chartConfig = useMemo<ChartConfig>(() => {\n    const cfg: ChartConfig = {};\n    for (const line of lines) {\n      cfg[line.dataKey] = { label: line.label, color: line.color };\n    }\n    return cfg;\n  }, [lines]);\n\n  if (data.length === 0) {\n    return (\n      <div className=\"flex h-full items-center justify-center\">\n        <p className=\"text-sm text-muted-foreground\">Geen data beschikbaar.</p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"h-full w-full\" onWheel={onWheel}>\n      <ChartContainer config={chartConfig} className=\"h-full w-full\">\n        <ComposedChart data={tijdData} margin={{ top: 10, right: 20, bottom: 0, left: 10 }}>\n          <defs>\n            {showWaterFill && (\n              <linearGradient id=\"dtc-waterGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                <stop offset=\"0%\" stopColor=\"#60a5fa\" stopOpacity={0.25} />\n                <stop offset=\"100%\" stopColor=\"#1e40af\" stopOpacity={0.45} />\n              </linearGradient>\n            )}\n          </defs>\n\n          <CartesianGrid strokeDasharray=\"3 3\" />\n\n          {/* Water zone (optional) */}\n          {showWaterFill && (\n            <ReferenceArea\n              y1={domain[0]}\n              y2={0}\n              fill=\"url(#dtc-waterGradient)\"\n              fillOpacity={1}\n              ifOverflow=\"hidden\"\n            />\n          )}\n\n          <XAxis\n            dataKey={TIJD_KEY}\n            type=\"number\"\n            scale=\"time\"\n            domain={as.domain}\n            ticks={as.ticks}\n            tickFormatter={as.formatTick}\n            fontSize={12}\n          />\n          <YAxis\n            domain={domain}\n            tickFormatter={(v: number) => getal(v, 1)}\n            fontSize={12}\n            label={{ value: yLabel, angle: -90, position: 'insideLeft', style: { fontSize: 12 } }}\n          />\n\n          <ChartTooltip\n            content={\n              <ChartTooltipContent\n                formatter={(value, name) => {\n                  const label = chartConfig[name as string]?.label ?? name;\n                  return `${label}: ${getal(value as number, 2)} ${yLabel}`;\n                }}\n                labelFormatter={(_, payload) => {\n                  const datum = payload?.[0]?.payload?.[dateKey];\n                  return typeof datum === 'string' ? datumLang(datum) : '';\n                }}\n              />\n            }\n          />\n\n          {/* Band boundaries (min/max range as dashed lines) */}\n          {bands?.map((band) => (\n            <Line\n              key={`${band.maxKey}-band`}\n              type=\"monotone\"\n              dataKey={band.maxKey}\n              stroke={band.color}\n              strokeWidth={1}\n              strokeDasharray=\"4 3\"\n              strokeOpacity={0.6}\n              dot={false}\n              connectNulls={false}\n              isAnimationActive={false}\n            />\n          ))}\n          {bands?.map((band) => (\n            <Line\n              key={`${band.minKey}-band`}\n              type=\"monotone\"\n              dataKey={band.minKey}\n              stroke={band.color}\n              strokeWidth={1}\n              strokeDasharray=\"4 3\"\n              strokeOpacity={0.6}\n              dot={false}\n              connectNulls={false}\n              isAnimationActive={false}\n            />\n          ))}\n\n          {/* Data lines */}\n          {lines.map((line) => (\n            <Line\n              key={line.dataKey}\n              type=\"monotone\"\n              dataKey={line.dataKey}\n              stroke={line.color}\n              strokeWidth={2}\n              dot={{ r: 3, fill: line.color }}\n              connectNulls={false}\n              isAnimationActive={false}\n            />\n          ))}\n\n          {/* NAP=0 reference */}\n          {showWaterFill && (\n            <ReferenceLine\n              y={0}\n              stroke={NAP_COLOR}\n              strokeWidth={1.5}\n              strokeDasharray=\"6 3\"\n              label={{ value: 'NAP', position: 'right', fill: NAP_COLOR, fontSize: 12 }}\n              ifOverflow=\"hidden\"\n            />\n          )}\n\n          {/* Reference levels */}\n          {referenceLevels &&\n            (\n              Object.entries(referenceLevels) as [\n                keyof typeof REF_LEVEL_COLORS,\n                number | undefined,\n              ][]\n            ).map(\n              ([key, value]) =>\n                value != null && (\n                  <ReferenceLine\n                    key={key}\n                    y={value}\n                    stroke={REF_LEVEL_COLORS[key]}\n                    strokeWidth={1.5}\n                    strokeDasharray=\"5 5\"\n                    label={{\n                      value: REF_LEVEL_LABELS[key],\n                      position: 'right',\n                      fill: REF_LEVEL_COLORS[key],\n                      fontSize: 12,\n                    }}\n                    ifOverflow=\"hidden\"\n                  />\n                )\n            )}\n        </ComposedChart>\n      </ChartContainer>\n    </div>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport { apiBaggervakkenList } from '@/js/api';\nimport { transformGeoJSON } from '@/js/lib/projections';\n\nexport function useBaggervakken() {\n  const [geojson, setGeojson] = useState<GeoJSON.FeatureCollection | null>(null);\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    let cancelled = false;\n\n    const fetch = async () => {\n      try {\n        const { data } = await apiBaggervakkenList({ query: { limit: 100 } });\n        if (cancelled) return;\n\n        const fc = data?.results;\n        if (!fc?.features?.length) {\n          setGeojson(null);\n          return;\n        }\n\n        // The API returns GeoJSON in EPSG:28992 — transform to WGS84\n        const transformed = transformGeoJSON(fc as unknown as GeoJSON.FeatureCollection);\n        setGeojson(transformed);\n      } catch {\n        if (!cancelled) setError('Baggervakken konden niet worden geladen.');\n      } finally {\n        if (!cancelled) setLoading(false);\n      }\n    };\n\n    fetch();\n    return () => {\n      cancelled = true;\n    };\n  }, []);\n\n  return { geojson, loading, error };\n}\n","import { useEffect, useMemo, useState } from 'react';\n\nimport { apiDatasetsList } from '@/js/api/sdk.gen';\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\n/**\n * Haal de meest recente non-ATP `ready` SurveyDataset per vak op.\n *\n * Gebruikt het backend-side `latest_per_vak=true` filter (zie\n * `SurveyDatasetViewSet.filter_queryset`), dus payload = ~14 rijen i.p.v.\n * de ~700 totale ready-datasets. Heatmap-cells per rij is ~360 ints,\n * dus totaal ~5 KB.\n *\n * `fout` staat apart van een lege lijst: een mislukte fetch is niet hetzelfde\n * als een vak dat nooit gepeild is, en het scherm moet dat verschil kunnen tonen.\n */\nexport function useLatestDatasets(refreshKey: number = 0) {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [fout, setFout] = useState(false);\n\n  useEffect(() => {\n    let cancelled = false;\n    setLoading(true);\n    setFout(false);\n\n    apiDatasetsList({\n      query: {\n        status: 'ready',\n        latest_per_vak: true,\n        limit: 100,\n      },\n    })\n      // Zonder throwOnError komt een HTTP-fout (403/500) hier binnen als\n      // `error`, niet in de catch; dat is net zo goed een mislukte fetch.\n      .then(({ data, error }) => {\n        if (cancelled) return;\n        setDatasets(data?.results ?? []);\n        if (error !== undefined) setFout(true);\n      })\n      .catch(() => {\n        if (!cancelled) {\n          setDatasets([]);\n          setFout(true);\n        }\n      })\n      .finally(() => {\n        if (!cancelled) setLoading(false);\n      });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [refreshKey]);\n\n  const latestByVak = useMemo(() => {\n    const map = new Map<string, SurveyDataset>();\n    for (const d of datasets) {\n      map.set(d.baggervak_code, d);\n    }\n    return map;\n  }, [datasets]);\n\n  return { latestByVak, loading, fout };\n}\n","import { isAtp, isVoorspelling } from './peilsoort';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\nexport type TrendRecord = Record<string, number | string | null>;\n\n/**\n * Pivot SurveyDataset[] into date-indexed records for Recharts.\n *\n * Uses the per-vak clipped stats (`depth_*_in_vak`) rather than the raster-wide\n * values — the raster-wide max typically lies outside the baggervak polygon\n * and is not a navigation-relevant value.\n *\n * Also pivots the six kuberingen fields (volume / surface above NGD/IVD/OHD)\n * so the VolumeTrendChart can consume the same records.\n *\n * Input:  flat list of datasets (possibly multiple vakken × dates)\n * Output: [{ date, C5_max, C5_ngd_volume, C5_ngd_surface, ... }, ...]\n *\n * ATP datasets are excluded — they cover only the post-dredging sub-area of\n * the vak, which gives misleading trend points. The KuberingTable renders the\n * raw `SurveyDataset[]` directly so ATP peilingen still show up there as\n * proof-of-delivery rows.\n */\nexport function pivotDatasets(\n  datasets: SurveyDataset[],\n  vakCodes: string[],\n): TrendRecord[] {\n  const byDate = new Map<string, TrendRecord>();\n  const vakSet = new Set(vakCodes);\n\n  for (const ds of datasets) {\n    if (!ds.survey_date || !vakSet.has(ds.baggervak_code)) continue;\n    if (isAtp(ds) || isVoorspelling(ds)) continue;\n\n    let record = byDate.get(ds.survey_date);\n    if (!record) {\n      record = { date: ds.survey_date };\n      byDate.set(ds.survey_date, record);\n    }\n\n    const code = ds.baggervak_code;\n    record[`${code}_max`] = ds.depth_max_in_vak;\n    record[`${code}_max_incl`] = ds.depth_max_incl_randstrook;\n    record[`${code}_mean`] = ds.depth_mean_in_vak;\n    record[`${code}_min`] = ds.depth_min_in_vak;\n    record[`${code}_ngd_volume`] = ds.volume_above_ngd_m3;\n    record[`${code}_ngd_surface`] = ds.surface_above_ngd_m2;\n    record[`${code}_ivd_volume`] = ds.volume_above_ivd_m3;\n    record[`${code}_ivd_surface`] = ds.surface_above_ivd_m2;\n    record[`${code}_ohd_volume`] = ds.volume_above_ohd_m3;\n    record[`${code}_ohd_surface`] = ds.surface_above_ohd_m2;\n  }\n\n  return Array.from(byDate.values()).sort((a, b) =>\n    (a.date as string).localeCompare(b.date as string),\n  );\n}\n\nconst VOLUME_KEYS = [\n  ['ngd_volume', 'volume_above_ngd_m3'],\n  ['ngd_surface', 'surface_above_ngd_m2'],\n  ['ivd_volume', 'volume_above_ivd_m3'],\n  ['ivd_surface', 'surface_above_ivd_m2'],\n  ['ohd_volume', 'volume_above_ohd_m3'],\n  ['ohd_surface', 'surface_above_ohd_m2'],\n] as const;\n\n/**\n * Add the forecast (\"niet baggeren\") kuberingen to the pivoted measurements\n * as separate `<code>_<metric>_<type>_verwachting` keys, so the chart can draw\n * them as a dashed continuation. The last measurement before the first\n * forecast gets the same keys with its measured value: the dashed line starts\n * where the measured line ends.\n */\nexport function withVoorspelling(\n  records: TrendRecord[],\n  voorspellingen: SurveyDataset[],\n  vakCodes: string[],\n): TrendRecord[] {\n  const vakSet = new Set(vakCodes);\n  const byDate = new Map(records.map((r) => [r.date as string, { ...r }]));\n  for (const code of vakSet) {\n    const eigen = voorspellingen\n      .filter((ds) => ds.baggervak_code === code && ds.survey_date && isVoorspelling(ds))\n      .sort((a, b) => a.survey_date!.localeCompare(b.survey_date!));\n    if (eigen.length === 0) continue;\n    const eerste = eigen[0].survey_date!;\n    const aanloop = [...byDate.values()]\n      .filter((r) => (r.date as string) < eerste && r[`${code}_ohd_volume`] != null)\n      .sort((a, b) => (b.date as string).localeCompare(a.date as string))[0];\n    if (aanloop) {\n      for (const [key] of VOLUME_KEYS) aanloop[`${code}_${key}_verwachting`] = aanloop[`${code}_${key}`];\n    }\n    for (const ds of eigen) {\n      const record = byDate.get(ds.survey_date!) ?? { date: ds.survey_date! };\n      for (const [key, veld] of VOLUME_KEYS) record[`${code}_${key}_verwachting`] = ds[veld] ?? null;\n      byDate.set(ds.survey_date!, record);\n    }\n  }\n  return Array.from(byDate.values()).sort((a, b) =>\n    (a.date as string).localeCompare(b.date as string),\n  );\n}\n","import type { ReactNode } from 'react';\n\ntype PageHeaderProps = {\n  title: string;\n  description?: ReactNode;\n  /** Rechts naast de titel, bijvoorbeeld een status of een knop. */\n  children?: ReactNode;\n};\n\n// De kopbalk is weg; elke pagina draagt zelf haar titel als h1.\nconst PageHeader = ({ title, description, children }: PageHeaderProps) => (\n  <header className=\"flex flex-wrap items-start gap-x-4 gap-y-2\">\n    <div className=\"min-w-0 flex-1\">\n      <h1 className=\"text-2xl font-semibold tracking-tight\">{title}</h1>\n      {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n    </div>\n    {children ? <div className=\"flex shrink-0 items-center gap-2\">{children}</div> : null}\n  </header>\n);\n\nexport default PageHeader;\n","import type { ReactNode } from 'react';\n\ntype PageSectionProps = {\n  title: string;\n  description?: ReactNode;\n  children?: ReactNode;\n};\n\nconst PageSection = ({ title, description, children }: PageSectionProps) => (\n  <section className=\"rounded-xl border bg-card p-5\">\n    <h2 className=\"text-lg font-semibold\">{title}</h2>\n    {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n    {children ? <div className=\"mt-4\">{children}</div> : null}\n  </section>\n);\n\nexport default PageSection;\n","import { useEffect, useMemo, useState } from 'react';\nimport { Link, useSearchParams } from 'react-router';\n\nimport { KuberingTable } from './KuberingTable';\nimport { VakDetailPanel } from './VakDetailPanel';\nimport { VakkenLijn, maakVakkenLijn } from './VakkenLijn';\nimport { VakOverviewTable, isPeilingVerouderd, type VakRowInput } from './VakOverviewTable';\nimport { VolumeTrendChart, type VolumeMetric, type VolumeType } from './VolumeTrendChart';\n\nimport { DepthTimeChart } from '@/js/components/DepthTimeChart';\nimport { Card, CardContent, CardHeader, CardTitle } from '@/js/components/ui/card';\nimport { ToggleGroup, ToggleGroupItem } from '@/js/components/ui/toggle-group';\nimport { useBaggervakken } from '@/js/hooks/use-baggervakken';\nimport { useLatestDatasets } from '@/js/hooks/use-latest-datasets';\nimport { useTrendDatasets } from '@/js/hooks/use-trend-datasets';\nimport { REF_LEVEL_COLORS, REF_LEVEL_LABELS } from '@/js/lib/reference-levels';\nimport { pivotDatasets, withVoorspelling } from '@/js/lib/trend-data';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\n// ---------------------------------------------------------------------------\n// Display modes\n// ---------------------------------------------------------------------------\n\ntype DisplayMode = 'shallowest' | 'shallowest_incl' | 'mean' | 'range';\n\nconst MODE_LABELS: Record<DisplayMode, string> = {\n  shallowest: 'Ondiepste excl. 5 m',\n  shallowest_incl: 'Ondiepste incl. 5 m',\n  mean: 'Gemiddelde',\n  range: 'Dieptebereik',\n};\n\nconst VOLUME_TYPE_LABELS: Record<VolumeType, string> = {\n  volume: 'Volume (m³)',\n  surface: 'Oppervlak (m²)',\n};\n\nconst LIJNKLEUR = '#3b82f6';\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Eén vak tegelijk: de tabel kiest het vak, alles eronder gaat over dat vak.\n *\n * Hiervoor stond onderaan een tweede keuze (vinkjes per vak) met eigen\n * kaarten, een eigen volumegrafiek en een eigen peilingtabel, die grotendeels\n * herhaalden wat het detailpaneel al liet zien.\n */\nexport default function AnalysePage() {\n  const { geojson, loading: vakLoading } = useBaggervakken();\n  const [mode, setMode] = useState<DisplayMode>('shallowest');\n  const [volumeType, setVolumeType] = useState<VolumeType>('volume');\n\n  // Laatste periodieke peiling per vak, voor de tabel.\n  const { latestByVak, loading: overviewLoading } = useLatestDatasets(0);\n\n  // `?vak=A4` komt van de knop Naar analyse in het werkblad van de kaart.\n  const [searchParams] = useSearchParams();\n  const [selectedCode, setSelectedCode] = useState<string | null>(() => searchParams.get('vak'));\n\n  // Initiële selectie: vak met hoogste m³ > NGD onder de vakken met een\n  // recente peiling. Een vak dat al een jaar niet gepeild is, hoort niet als\n  // eerste in beeld. Loopt een keer; daarna stuurt de gebruiker.\n  useEffect(() => {\n    if (selectedCode != null) return;\n    let bestCode: string | null = null;\n    let bestValue = -Infinity;\n    for (const [code, ds] of latestByVak.entries()) {\n      if (isPeilingVerouderd(ds.survey_date)) continue;\n      const v = ds.volume_above_ngd_m3;\n      if (v != null && v > bestValue) {\n        bestValue = v;\n        bestCode = code;\n      }\n    }\n    if (bestCode == null && latestByVak.size > 0) {\n      bestCode = [...latestByVak.keys()][0];\n    }\n    if (bestCode != null) setSelectedCode(bestCode);\n  }, [latestByVak, selectedCode]);\n\n  const selectedVakken = useMemo(() => (selectedCode ? [selectedCode] : []), [selectedCode]);\n  const { datasets, voorspellingen, loading: dsLoading } = useTrendDatasets(selectedVakken, 0);\n\n  const overviewRows = useMemo<VakRowInput[]>(() => {\n    if (!geojson) return [];\n    return geojson.features\n      .filter((f) => typeof f.properties?.code === 'string')\n      .map((f) => {\n        const code = f.properties!.code as string;\n        const angleDeg = (f.properties!.obb_angle_deg as number | null | undefined) ?? 0;\n        const widthM = (f.properties!.obb_width_m as number | null | undefined) ?? 100;\n        const heightM = (f.properties!.obb_height_m as number | null | undefined) ?? 20;\n        const geom = f.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon;\n        return {\n          code,\n          polygon: geom,\n          angleDeg,\n          widthM,\n          heightM,\n          dataset: latestByVak.get(code) ?? null,\n        };\n      });\n  }, [geojson, latestByVak]);\n\n  const vakkenLijn = useMemo(\n    () => (geojson ? maakVakkenLijn(geojson.features, latestByVak) : null),\n    [geojson, latestByVak]\n  );\n\n  const selectedRow = useMemo(\n    () => overviewRows.find((r) => r.code === selectedCode) ?? null,\n    [overviewRows, selectedCode]\n  );\n\n  const levels = useMemo(() => {\n    const props = geojson?.features.find((f) => f.properties?.code === selectedCode)?.properties;\n    return {\n      ngd: (props?.ngd_level as number | null | undefined) ?? null,\n      ohd: (props?.ohd_level as number | null | undefined) ?? null,\n    };\n  }, [geojson, selectedCode]);\n\n  const chartData = useMemo(\n    () => pivotDatasets(datasets, selectedVakken),\n    [datasets, selectedVakken]\n  );\n  // Only the kubering chart shows the forecast; the depth chart stays measurements only.\n  const volumeData = useMemo(\n    () => withVoorspelling(chartData, voorspellingen, selectedVakken),\n    [chartData, voorspellingen, selectedVakken]\n  );\n\n  const lines = useMemo(() => {\n    if (!selectedCode) return [];\n    const dataKey =\n      mode === 'shallowest'\n        ? `${selectedCode}_max`\n        : mode === 'shallowest_incl'\n          ? `${selectedCode}_max_incl`\n          : `${selectedCode}_mean`; // bereik toont het gemiddelde met een band\n    const label = mode === 'range' ? 'Gemiddelde' : MODE_LABELS[mode];\n    return [{ dataKey, color: LIJNKLEUR, label }];\n  }, [selectedCode, mode]);\n\n  const bands = useMemo(\n    () =>\n      mode === 'range' && selectedCode\n        ? [{ minKey: `${selectedCode}_min`, maxKey: `${selectedCode}_max`, color: LIJNKLEUR }]\n        : undefined,\n    [selectedCode, mode]\n  );\n\n  const volumeLines = useMemo(() => {\n    if (!selectedCode) return [];\n    const suffix = volumeType === 'volume' ? 'volume' : 'surface';\n    const metrics: VolumeMetric[] = ['ngd', 'ohd'];\n    return [\n      ...metrics.map((metric) => ({\n        dataKey: `${selectedCode}_${metric}_${suffix}`,\n        color: REF_LEVEL_COLORS[metric],\n        label: `boven ${REF_LEVEL_LABELS[metric]}`,\n      })),\n      ...metrics.map((metric) => ({\n        dataKey: `${selectedCode}_${metric}_${suffix}_verwachting`,\n        color: REF_LEVEL_COLORS[metric],\n        label: `boven ${REF_LEVEL_LABELS[metric]}, verwachting (niet baggeren)`,\n        dashed: true,\n      })),\n    ];\n  }, [selectedCode, volumeType]);\n\n  const referenceLevels = useMemo(\n    () => ({\n      ngd: levels.ngd ?? undefined,\n      ohd: levels.ohd ?? undefined,\n    }),\n    [levels]\n  );\n\n  const loading = vakLoading || dsLoading;\n  const laden = (\n    <div className=\"flex h-64 items-center justify-center\">\n      <p className=\"text-sm text-muted-foreground\">Laden...</p>\n    </div>\n  );\n\n  return (\n    <div className=\"grid min-w-0 grid-cols-[minmax(0,1fr)] gap-4\">\n      <PageHeader\n        title=\"Analyse\"\n        description=\"De laatste periodieke peiling per vak. Klik op een vak voor de details.\"\n      />\n      {/* De lijn en de tabel kiezen hetzelfde vak: één selectie, twee ingangen. */}\n      {!overviewLoading && vakkenLijn && vakkenLijn.lijn.length > 0 && (\n        <VakkenLijn\n          lijn={vakkenLijn.lijn}\n          los={vakkenLijn.los}\n          selectedCode={selectedCode}\n          onSelect={setSelectedCode}\n        />\n      )}\n      <PageSection\n        title=\"Overzicht baggervakken\"\n        description=\"m³ en % gelden voor het vak zonder de randstrook van 5 m. Een % is van het hele vak: wat niet gemeten is, telt daarin niet als overschrijding. Klik op een kolomkop om anders te sorteren.\"\n      >\n        {overviewLoading || overviewRows.length === 0 ? (\n          <div className=\"flex h-32 items-center justify-center text-sm text-muted-foreground\">\n            Overzicht laden…\n          </div>\n        ) : (\n          <div className=\"overflow-x-auto\">\n            <VakOverviewTable\n              rows={overviewRows}\n              selectedCode={selectedCode}\n              onSelect={setSelectedCode}\n            />\n          </div>\n        )}\n      </PageSection>\n\n      {selectedRow && (\n        <VakDetailPanel\n          code={selectedRow.code}\n          polygon={selectedRow.polygon}\n          angleDeg={selectedRow.angleDeg}\n          levels={levels}\n          latestDataset={selectedRow.dataset}\n          trends={\n            <>\n              <Card>\n                <CardHeader className=\"flex flex-row flex-wrap items-center justify-between gap-2\">\n                  <CardTitle className=\"text-sm\">Diepte over tijd</CardTitle>\n                  <ToggleGroup\n                    type=\"single\"\n                    value={mode}\n                    onValueChange={(v) => {\n                      if (v) setMode(v as DisplayMode);\n                    }}\n                    size=\"sm\"\n                  >\n                    {(Object.entries(MODE_LABELS) as [DisplayMode, string][]).map(\n                      ([key, label]) => (\n                        <ToggleGroupItem key={key} value={key}>\n                          {label}\n                        </ToggleGroupItem>\n                      )\n                    )}\n                  </ToggleGroup>\n                </CardHeader>\n                <CardContent>\n                  {loading ? (\n                    laden\n                  ) : (\n                    <div className=\"h-72\">\n                      <DepthTimeChart\n                        data={chartData}\n                        dateKey=\"date\"\n                        lines={lines}\n                        bands={bands}\n                        referenceLevels={referenceLevels}\n                      />\n                    </div>\n                  )}\n                  <p className=\"mt-2 text-xs text-muted-foreground\">\n                    Alleen periodieke peilingen; een aantoonpeiling dekt maar een deel van het vak.\n                  </p>\n                </CardContent>\n              </Card>\n\n              <Card>\n                <CardHeader className=\"flex flex-row flex-wrap items-center justify-between gap-2\">\n                  <CardTitle className=\"text-sm\">Kubering over tijd</CardTitle>\n                  <ToggleGroup\n                    type=\"single\"\n                    value={volumeType}\n                    onValueChange={(v) => {\n                      if (v) setVolumeType(v as VolumeType);\n                    }}\n                    size=\"sm\"\n                  >\n                    {(Object.entries(VOLUME_TYPE_LABELS) as [VolumeType, string][]).map(\n                      ([key, label]) => (\n                        <ToggleGroupItem key={key} value={key}>\n                          {label}\n                        </ToggleGroupItem>\n                      )\n                    )}\n                  </ToggleGroup>\n                </CardHeader>\n                <CardContent>\n                  {loading ? (\n                    laden\n                  ) : (\n                    <div className=\"h-72\">\n                      <VolumeTrendChart\n                        data={volumeData}\n                        dateKey=\"date\"\n                        lines={volumeLines}\n                        yLabel={volumeType === 'volume' ? 'm³ boven niveau' : 'm² boven niveau'}\n                        unit={volumeType === 'volume' ? 'm³' : 'm²'}\n                      />\n                    </div>\n                  )}\n                  {voorspellingen.length > 0 && (\n                    <p className=\"mt-2 text-xs text-muted-foreground\">\n                      Stippellijn: de verwachting als er vanaf de laatste peiling niet meer\n                      gebaggerd wordt (dezelfde voorspelling als op de kaarttijdlijn).\n                    </p>\n                  )}\n                </CardContent>\n              </Card>\n            </>\n          }\n          peilingen={\n            <Card>\n              <CardHeader>\n                <CardTitle className=\"text-sm\">Kuberingen per peildatum</CardTitle>\n              </CardHeader>\n              <CardContent className=\"overflow-x-auto\">\n                <KuberingTable datasets={datasets} vakCodes={selectedVakken} />\n              </CardContent>\n            </Card>\n          }\n        />\n      )}\n\n      <PageSection\n        title=\"Omgevingsmonitor\"\n        description=\"De bodem rond de hele Eemsgeul per jaar, met de verschuiving van de platen en drie jaar vooruit.\"\n      >\n        <Link to=\"/app/plaatmigratie\" className=\"text-sm font-medium underline underline-offset-4\">\n          Naar de Omgevingsmonitor\n        </Link>\n      </PageSection>\n    </div>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\nimport { apiDatasetsList } from '@/js/api/sdk.gen';\nimport { isVoorspelling } from '@/js/lib/peilsoort';\n\n/**\n * Fetch ALL ready datasets for the given baggervak codes (no date filter,\n * no latest_per_vak). Used by the Analyse page for trend visualization.\n *\n * `refreshKey` is an optional opaque value; changing it forces a refetch\n * even if `vakCodes` didn't change (e.g. after the caller back-filled\n * per-vak statistics on the server).\n */\nexport function useTrendDatasets(vakCodes: string[], refreshKey: number = 0) {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [voorspellingen, setVoorspellingen] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(false);\n\n  // The effect depends on the *contents* of `vakCodes`, not its array identity.\n  // Serialising to a string is the simplest way to get referential stability\n  // without pulling in a deep-compare effect hook.\n  const vakCodesKey = vakCodes.join(',');\n\n  useEffect(() => {\n    if (vakCodes.length === 0) {\n      setDatasets([]);\n      setVoorspellingen([]);\n      setLoading(false);\n      return;\n    }\n\n    let cancelled = false;\n    setLoading(true);\n\n    apiDatasetsList({\n      query: {\n        status: 'ready',\n        baggervak: vakCodes,\n        limit: 2000,\n      },\n    })\n      .then(({ data }) => {\n        // Een voorspelling is geen meting: niet in de tabel en niet in de CSV,\n        // wel apart beschikbaar als stippellijn in de kuberingsgrafiek.\n        if (cancelled) return;\n        const alle = data?.results ?? [];\n        setDatasets(alle.filter((ds) => !isVoorspelling(ds)));\n        setVoorspellingen(alle.filter((ds) => isVoorspelling(ds)));\n      })\n      .catch(() => {\n        if (!cancelled) {\n          setDatasets([]);\n          setVoorspellingen([]);\n        }\n      })\n      .finally(() => {\n        if (!cancelled) setLoading(false);\n      });\n\n    return () => {\n      cancelled = true;\n    };\n    // Depending on `vakCodesKey` (the joined string) instead of `vakCodes`\n    // prevents re-fetching on every render for a fresh array with identical\n    // contents; see the vakCodesKey comment above. `refreshKey` is included\n    // so callers can force a refetch after mutating server-side state.\n  }, [vakCodesKey, refreshKey]);\n\n  return { datasets, voorspellingen, loading };\n}\n","import * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-10 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\n        'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',\n        'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n","import { Separator as SeparatorPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  decorative = true,\n  ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n  return (\n    <SeparatorPrimitive.Root\n      data-slot=\"separator\"\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n","'use client';\n\nimport { XIcon } from 'lucide-react';\nimport { Dialog as SheetPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n  return (\n    <SheetPrimitive.Overlay\n      data-slot=\"sheet-overlay\"\n      className={cn(\n        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Content\n        data-slot=\"sheet-content\"\n        className={cn(\n          'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',\n          side === 'right' &&\n            'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',\n          side === 'left' &&\n            'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',\n          side === 'top' &&\n            'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',\n          side === 'bottom' &&\n            'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n            <XIcon className=\"size-4\" />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Content>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn('flex flex-col gap-1.5 p-4', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn('text-foreground font-semibold', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n","import { cn } from '@/js/lib/utils';\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn('bg-accent animate-pulse rounded-md', className)}\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n","import { Tooltip as TooltipPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n","'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport { Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { Button } from '@/js/components/ui/button';\nimport { Input } from '@/js/components/ui/input';\nimport { Separator } from '@/js/components/ui/separator';\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from '@/js/components/ui/sheet';\nimport { Skeleton } from '@/js/components/ui/skeleton';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '@/js/components/ui/tooltip';\nimport { useIsMobile } from '@/js/hooks/use-mobile';\nimport { cn } from '@/js/lib/utils';\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen, setOpenMobile]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <TooltipProvider delayDuration={0}>\n        <div\n          data-slot=\"sidebar-wrapper\"\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH,\n              '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n              ...style,\n            } as React.CSSProperties\n          }\n          className={cn(\n            'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TooltipProvider>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          data-mobile=\"true\"\n          className=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n          side={side}\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer text-sidebar-foreground hidden md:block\"\n      data-state={state}\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-variant={variant}\n      data-side={side}\n      data-slot=\"sidebar\"\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        data-slot=\"sidebar-gap\"\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n      />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',\n          side === 'left'\n            ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'\n            : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn('size-7', className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle Sidebar\"\n      className={cn(\n        'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        'bg-background relative flex w-full flex-1 flex-col',\n        'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn('bg-background h-8 w-full shadow-none', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn('bg-sidebar-border mx-2 w-auto', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<'div'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'div';\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: React.ComponentProps<'button'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : 'button';\n\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\n      data-sidebar=\"group-action\"\n      className={cn(\n        'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        // Increases the hit area of the button on mobile.\n        'after:absolute after:-inset-2 md:after:hidden',\n        'group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn('w-full text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn('flex w-full min-w-0 flex-col gap-1', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn('group/menu-item relative', className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: React.ComponentProps<'button'> & {\n  asChild?: boolean;\n  isActive?: boolean;\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = asChild ? Slot.Root : 'button';\n  const { isMobile, state } = useSidebar();\n\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\n      data-sidebar=\"menu-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n      {...props}\n    />\n  );\n\n  if (!tooltip) {\n    return button;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent\n        side=\"right\"\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: React.ComponentProps<'button'> & {\n  asChild?: boolean;\n  showOnHover?: boolean;\n}) {\n  const Comp = asChild ? Slot.Root : 'button';\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\n      data-sidebar=\"menu-action\"\n      className={cn(\n        'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',\n        // Increases the hit area of the button on mobile.\n        'after:absolute after:-inset-2 md:after:hidden',\n        'peer-data-[size=sm]/menu-button:top-1',\n        'peer-data-[size=default]/menu-button:top-1.5',\n        'peer-data-[size=lg]/menu-button:top-2.5',\n        'group-data-[collapsible=icon]:hidden',\n        showOnHover &&\n          'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuBadge({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',\n        'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',\n        'peer-data-[size=sm]/menu-button:top-1',\n        'peer-data-[size=default]/menu-button:top-1.5',\n        'peer-data-[size=lg]/menu-button:top-2.5',\n        'group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const width = React.useMemo(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  }, []);\n\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      {...props}\n    >\n      {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',\n        'group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      data-sidebar=\"menu-sub-item\"\n      className={cn('group/menu-sub-item relative', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: React.ComponentProps<'a'> & {\n  asChild?: boolean;\n  size?: 'sm' | 'md';\n  isActive?: boolean;\n}) {\n  const Comp = asChild ? Slot.Root : 'a';\n\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\n      data-sidebar=\"menu-sub-button\"\n      data-size={size}\n      data-active={isActive}\n      className={cn(\n        'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',\n        'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',\n        size === 'sm' && 'text-xs',\n        size === 'md' && 'text-sm',\n        'group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n","import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n","import {\n  IconArrowWaveRightUp,\n  IconChartLine,\n  IconClock,\n  IconDatabase,\n  IconMap2,\n  IconRipple,\n  IconTrash,\n  IconUpload,\n} from '@tabler/icons-react';\nimport { LogOut } from 'lucide-react';\nimport { Link, NavLink, useLocation } from 'react-router';\n\nimport type { AuthUser } from '@/js/auth/types';\nimport type { ComponentProps, ComponentType } from 'react';\n\nimport mark from '@/images/archx-mark.png';\nimport logo from '@/images/logo-archx-600.png';\nimport { useAuth } from '@/js/auth';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarTrigger,\n} from '@/js/components/ui/sidebar';\nimport { cn } from '@/js/lib/utils';\n\ntype SidebarItem = {\n  title: string;\n  to: string;\n  icon: ComponentType<{ className?: string }>;\n  end?: boolean;\n};\n\ntype GatedSidebarItem = SidebarItem & { requiresAnalyseAccess?: boolean };\n\nconst MAIN_ITEMS: GatedSidebarItem[] = [\n  { title: 'Kaart', to: '/app/kaart', icon: IconMap2, end: true },\n  { title: 'Omgevingsmonitor', to: '/app/plaatmigratie', icon: IconRipple },\n  { title: 'Analyse', to: '/app/analyse', icon: IconChartLine, requiresAnalyseAccess: true },\n  {\n    title: 'Zandstromen',\n    to: '/app/zandstromen',\n    icon: IconArrowWaveRightUp,\n    requiresAnalyseAccess: true,\n  },\n];\n\nconst BEHEER_ITEMS: SidebarItem[] = [\n  { title: 'Uploads', to: '/app/data/uploads', icon: IconUpload },\n  { title: 'Datasets', to: '/app/data/datasets', icon: IconDatabase },\n  { title: 'Verwerking', to: '/app/data/jobs', icon: IconClock },\n  { title: 'Prullenbak', to: '/app/data/trash', icon: IconTrash },\n];\n\n// Actief = getij-blauw op het gekozen-vlak; hover blijft het grijze sidebar-accent.\nconst ACTIEF =\n  'data-[active=true]:bg-gekozen data-[active=true]:font-semibold data-[active=true]:text-primary data-[active=true]:hover:bg-gekozen data-[active=true]:hover:text-primary';\n\nfunction SidebarNavList({ items, beheer = false }: { items: SidebarItem[]; beheer?: boolean }) {\n  const location = useLocation();\n\n  return (\n    <SidebarMenu className={beheer ? 'gap-0' : 'gap-0.5'}>\n      {items.map((item) => {\n        const isActive = item.end\n          ? location.pathname === item.to\n          : location.pathname === item.to || location.pathname.startsWith(`${item.to}/`);\n\n        return (\n          <SidebarMenuItem key={item.to}>\n            <SidebarMenuButton\n              asChild\n              isActive={isActive}\n              tooltip={item.title}\n              className={cn(\n                'gap-3 rounded-md px-3',\n                beheer\n                  ? 'h-9 text-[13px] text-muted-foreground [&>svg]:size-4'\n                  : 'h-10 text-sm text-leisteen [&>svg]:size-[18px]',\n                ACTIEF\n              )}\n            >\n              <NavLink to={item.to} end={item.end}>\n                <item.icon />\n                <span>{item.title}</span>\n              </NavLink>\n            </SidebarMenuButton>\n          </SidebarMenuItem>\n        );\n      })}\n    </SidebarMenu>\n  );\n}\n\n/**\n * Naam en initialen voor onderin de zijbalk. Meestal heeft het account alleen een\n * e-mailadres (allauth `display` is dan dat adres), dus komt de naam uit het deel\n * voor de @: \"jelle.kalsbeek\" wordt \"Jelle Kalsbeek\", \"j.kalsbeek\" \"J. Kalsbeek\".\n */\nexport function gebruikersnaam(user: Pick<AuthUser, 'display' | 'email'> | null | undefined): {\n  naam: string;\n  initialen: string;\n} {\n  if (!user) return { naam: 'Onbekende gebruiker', initialen: '?' };\n  const eigenNaam = user.display && user.display !== user.email ? user.display.trim() : null;\n  const delen = (eigenNaam ?? user.email.split('@')[0]).split(/[\\s._-]+/).filter(Boolean);\n  if (delen.length === 0) return { naam: user.email, initialen: '?' };\n  // Een echte weergavenaam blijft zoals hij is (\"Anne de Vries\", niet \"De\").\n  const naam =\n    eigenNaam ??\n    delen\n      .map((deel) =>\n        deel.length === 1 ? `${deel.toUpperCase()}.` : deel[0].toUpperCase() + deel.slice(1)\n      )\n      .join(' ');\n  const initialen = (\n    delen.length === 1 ? delen[0][0] : delen[0][0] + delen[delen.length - 1][0]\n  ).toUpperCase();\n  return { naam, initialen };\n}\n\nexport function AppSidebar({ ...props }: ComponentProps<typeof Sidebar>) {\n  const { user, logout } = useAuth();\n  const mainItems = MAIN_ITEMS.filter(\n    (item) => !item.requiresAnalyseAccess || user?.has_analyse_access\n  );\n  const { naam, initialen } = gebruikersnaam(user);\n\n  return (\n    <Sidebar collapsible=\"icon\" {...props}>\n      <SidebarHeader className=\"flex-row items-center gap-2 pt-5 pr-2 pb-4 pl-4 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:px-2 group-data-[collapsible=icon]:pt-4\">\n        <Link\n          to=\"/app/kaart\"\n          className=\"shrink-0 rounded-md outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n        >\n          <img\n            src={logo}\n            alt=\"ARCH.X Digital Twin voor vaarwegen\"\n            className=\"block w-[196px] group-data-[collapsible=icon]:hidden\"\n          />\n          {/* Ingeklapt past alleen het merkteken (de X met het baken). */}\n          <img\n            src={mark}\n            alt=\"ARCH.X\"\n            className=\"hidden h-8 w-auto group-data-[collapsible=icon]:block\"\n          />\n        </Link>\n        <SidebarTrigger\n          className=\"ml-auto text-muted-foreground group-data-[collapsible=icon]:ml-0\"\n          aria-label=\"Zijbalk in- of uitklappen\"\n        />\n      </SidebarHeader>\n\n      <div className=\"mx-3 mb-3 flex flex-col gap-px rounded-[10px] bg-muted px-3 py-2.5 group-data-[collapsible=icon]:hidden\">\n        <span className=\"text-xs text-muted-foreground\">Vaarweg</span>\n        <span className=\"text-[15px] font-semibold\">Eemsgeul</span>\n      </div>\n\n      <SidebarContent className=\"gap-0\">\n        <SidebarGroup className=\"px-3 py-1 group-data-[collapsible=icon]:px-2\">\n          <SidebarGroupContent>\n            <nav aria-label=\"Hoofdmenu\">\n              <SidebarNavList items={mainItems} />\n            </nav>\n          </SidebarGroupContent>\n        </SidebarGroup>\n\n        {/* shadcn laat het kopje ingeklapt onzichtbaar in de opbouw staan; dan ligt\n            het over het onderste stuk van het pictogram erboven en vangt het de\n            klik. Ingeklapt dus echt weg. */}\n        <SidebarGroup className=\"px-3 pt-4 group-data-[collapsible=icon]:px-2 group-data-[collapsible=icon]:pt-2\">\n          <SidebarGroupLabel className=\"h-auto px-3 pb-1.5 text-xs font-semibold text-muted-foreground group-data-[collapsible=icon]:hidden\">\n            Beheer\n          </SidebarGroupLabel>\n          <SidebarGroupContent>\n            <nav aria-label=\"Beheer\">\n              <SidebarNavList items={BEHEER_ITEMS} beheer />\n            </nav>\n          </SidebarGroupContent>\n        </SidebarGroup>\n      </SidebarContent>\n\n      <SidebarFooter className=\"p-3 group-data-[collapsible=icon]:p-2\">\n        <div className=\"flex items-center gap-2.5 border-t pt-3 pl-1 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:pl-0\">\n          <span\n            aria-hidden=\"true\"\n            className=\"flex size-[34px] shrink-0 items-center justify-center rounded-full bg-leisteen text-[13px] font-semibold text-white group-data-[collapsible=icon]:size-8\"\n          >\n            {initialen}\n          </span>\n          <span className=\"flex min-w-0 flex-col leading-tight group-data-[collapsible=icon]:hidden\">\n            <span className=\"truncate text-[13px] font-semibold\">{naam}</span>\n            <span className=\"truncate text-xs text-muted-foreground\">\n              {user?.email ?? 'Onbekende gebruiker'}\n            </span>\n          </span>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"ml-auto size-[34px] shrink-0 text-muted-foreground hover:bg-muted hover:text-foreground group-data-[collapsible=icon]:ml-0 group-data-[collapsible=icon]:size-8\"\n            onClick={logout}\n            aria-label=\"Uitloggen\"\n            title=\"Uitloggen\"\n          >\n            <LogOut className=\"size-[17px]\" />\n          </Button>\n        </div>\n      </SidebarFooter>\n    </Sidebar>\n  );\n}\n","import {\n  CircleCheckIcon,\n  InfoIcon,\n  Loader2Icon,\n  OctagonXIcon,\n  TriangleAlertIcon,\n} from 'lucide-react';\nimport { useTheme } from 'next-themes';\nimport { Toaster as Sonner, type ToasterProps } from 'sonner';\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = 'system' } = useTheme();\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps['theme']}\n      className=\"toaster group\"\n      icons={{\n        success: <CircleCheckIcon className=\"size-4\" />,\n        info: <InfoIcon className=\"size-4\" />,\n        warning: <TriangleAlertIcon className=\"size-4\" />,\n        error: <OctagonXIcon className=\"size-4\" />,\n        loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n      }}\n      style={\n        {\n          '--normal-bg': 'var(--popover)',\n          '--normal-text': 'var(--popover-foreground)',\n          '--normal-border': 'var(--border)',\n          '--border-radius': 'var(--radius)',\n        } as React.CSSProperties\n      }\n      {...props}\n    />\n  );\n};\n\nexport { Toaster };\n","import { Component, lazy, Suspense, type ErrorInfo, type ReactNode } from 'react';\nimport { Navigate } from 'react-router';\n\nimport { useAuth } from '@/js/auth';\nimport { Button } from '@/js/components/ui/button';\n\n// Own chunk: the page brings the particle shaders and the story, and only users\n// with analysis access ever open it.\nconst ZandstromenPage = lazy(() => import('@/js/pages/zandstromen/ZandstromenPage'));\n\n/**\n * Catches a failed chunk load (a deploy since the tab was opened removes the old\n * chunk, or the network drops) and any error in the page itself. The router has no\n * errorElement, so without this its bare error screen would replace the whole app,\n * menu included.\n */\nexport class ZandstromenFoutgrens extends Component<\n  { children: ReactNode; /** Defaults to a full page reload. */ opnieuw?: () => void },\n  { fout: boolean }\n> {\n  state = { fout: false };\n\n  static getDerivedStateFromError() {\n    return { fout: true };\n  }\n\n  componentDidCatch(fout: Error, info: ErrorInfo) {\n    console.error('Zandstromen kon niet getoond worden', fout, info.componentStack);\n  }\n\n  render() {\n    if (!this.state.fout) return this.props.children;\n    return (\n      <div role=\"alert\" className=\"flex max-w-md flex-col items-start gap-3 p-6 text-sm\">\n        <p>\n          Zandstromen kon niet geladen worden. Misschien is het dashboard net bijgewerkt, of was de\n          verbinding even weg.\n        </p>\n        <Button\n          type=\"button\"\n          onClick={() => (this.props.opnieuw ?? (() => window.location.reload()))()}\n        >\n          Pagina opnieuw laden\n        </Button>\n      </div>\n    );\n  }\n}\n\nconst ZandstromenRoute = () => {\n  const { user } = useAuth();\n\n  if (!user?.has_analyse_access) {\n    return <Navigate to=\"/app/kaart\" replace />;\n  }\n\n  return (\n    <ZandstromenFoutgrens>\n      <Suspense\n        fallback={<div className=\"p-6 text-sm text-muted-foreground\">Zandstromen laden…</div>}\n      >\n        <ZandstromenPage />\n      </Suspense>\n    </ZandstromenFoutgrens>\n  );\n};\n\nexport default ZandstromenRoute;\n","'use client';\n\nimport { Label as LabelPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {\n  return (\n    <LabelPrimitive.Root\n      data-slot=\"label\"\n      className={cn(\n        'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n","import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport { Select as SelectPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {\n  return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {\n  return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {\n  return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-10 data-[size=sm]:h-9 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon asChild>\n        <ChevronDownIcon className=\"size-4 opacity-50\" />\n      </SelectPrimitive.Icon>\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  position = 'item-aligned',\n  align = 'center',\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Content\n        data-slot=\"select-content\"\n        className={cn(\n          'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',\n          position === 'popper' &&\n            'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n          className\n        )}\n        position={position}\n        align={align}\n        {...props}\n      >\n        <SelectScrollUpButton />\n        <SelectPrimitive.Viewport\n          className={cn(\n            'p-1',\n            position === 'popper' &&\n              'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1'\n          )}\n        >\n          {children}\n        </SelectPrimitive.Viewport>\n        <SelectScrollDownButton />\n      </SelectPrimitive.Content>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n  return (\n    <SelectPrimitive.Label\n      data-slot=\"select-label\"\n      className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        data-slot=\"select-item-indicator\"\n        className=\"absolute right-2 flex size-3.5 items-center justify-center\"\n      >\n        <SelectPrimitive.ItemIndicator>\n          <CheckIcon className=\"size-4\" />\n        </SelectPrimitive.ItemIndicator>\n      </span>\n      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n  return (\n    <SelectPrimitive.ScrollUpButton\n      data-slot=\"select-scroll-up-button\"\n      className={cn('flex cursor-default items-center justify-center py-1', className)}\n      {...props}\n    >\n      <ChevronUpIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollUpButton>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n  return (\n    <SelectPrimitive.ScrollDownButton\n      data-slot=\"select-scroll-down-button\"\n      className={cn('flex cursor-default items-center justify-center py-1', className)}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </SelectPrimitive.ScrollDownButton>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n","import { ArrowDown, ArrowUp, ArrowUpDown, Trash2 } from 'lucide-react';\nimport { useMemo, useEffect, useState } from 'react';\nimport { Link } from 'react-router';\n\nimport type { Status652Enum, SurveyDataset } from '@/js/api';\n\nimport { apiDatasetsList, apiDatasetsSoftDeleteCreate } from '@/js/api';\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport { Input } from '@/js/components/ui/input';\nimport { Label } from '@/js/components/ui/label';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/js/components/ui/select';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/js/components/ui/table';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\nconst STATUS_LABELS: Record<\n  Status652Enum,\n  { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' }\n> = {\n  uploaded: { label: 'Geüpload', variant: 'secondary' },\n  validating: { label: 'Valideren…', variant: 'default' },\n  validated: { label: 'Gevalideerd', variant: 'default' },\n  processing: { label: 'Verwerken…', variant: 'default' },\n  ready: { label: 'Gereed', variant: 'outline' },\n  published: { label: 'Gepubliceerd', variant: 'outline' },\n  archived: { label: 'Gearchiveerd', variant: 'secondary' },\n  deleted: { label: 'Verwijderd', variant: 'destructive' },\n};\n\ntype SortKey = 'baggervak_code' | 'survey_date' | 'status' | 'depth_min';\ntype SortDir = 'asc' | 'desc';\n\nfunction SortableHead({\n  label,\n  sortKey,\n  activeKey,\n  activeDir,\n  onSort,\n}: {\n  label: string;\n  sortKey: SortKey;\n  activeKey: SortKey;\n  activeDir: SortDir;\n  onSort: (key: SortKey) => void;\n}) {\n  const Icon = activeKey === sortKey ? (activeDir === 'asc' ? ArrowUp : ArrowDown) : ArrowUpDown;\n\n  return (\n    <TableHead>\n      <button\n        type=\"button\"\n        className=\"inline-flex items-center gap-1 hover:text-foreground\"\n        onClick={() => onSort(sortKey)}\n      >\n        {label}\n        <Icon className=\"size-3.5\" />\n      </button>\n    </TableHead>\n  );\n}\n\nconst DataDatasets = () => {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [baggervakFilter, setBaggervakFilter] = useState('');\n  const [statusFilter, setStatusFilter] = useState('');\n  const [sortKey, setSortKey] = useState<SortKey>('baggervak_code');\n  const [sortDir, setSortDir] = useState<SortDir>('asc');\n\n  const fetchDatasets = async () => {\n    setLoading(true);\n    try {\n      const { data } = await apiDatasetsList({\n        query: {\n          limit: 1000,\n          ...(baggervakFilter && { baggervak: [baggervakFilter] }),\n          ...(statusFilter.trim() && { status: statusFilter }),\n        },\n      });\n      const results = data?.results ?? [];\n      setDatasets(results.filter((ds) => ds.status !== 'deleted'));\n    } catch {\n      setDatasets([]);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  useEffect(() => {\n    fetchDatasets();\n  }, [baggervakFilter, statusFilter]);\n\n  const handleSort = (key: SortKey) => {\n    if (key === sortKey) {\n      setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));\n    } else {\n      setSortKey(key);\n      setSortDir('asc');\n    }\n  };\n\n  const sortedDatasets = useMemo(() => {\n    const dir = sortDir === 'asc' ? 1 : -1;\n    return [...datasets].sort((a, b) => {\n      let av: string | number | null;\n      let bv: string | number | null;\n      switch (sortKey) {\n        case 'baggervak_code':\n          av = a.baggervak_code ?? '';\n          bv = b.baggervak_code ?? '';\n          return dir * (av as string).localeCompare(bv as string);\n        case 'survey_date':\n          av = a.survey_date ?? '';\n          bv = b.survey_date ?? '';\n          return dir * (av as string).localeCompare(bv as string);\n        case 'status':\n          av = a.status ?? '';\n          bv = b.status ?? '';\n          return dir * (av as string).localeCompare(bv as string);\n        case 'depth_min':\n          av = a.depth_min ?? -Infinity;\n          bv = b.depth_min ?? -Infinity;\n          return dir * ((av as number) - (bv as number));\n        default:\n          return 0;\n      }\n    });\n  }, [datasets, sortKey, sortDir]);\n\n  const handleDelete = async (id: number) => {\n    await apiDatasetsSoftDeleteCreate({ path: { id } });\n    setDatasets((prev) => prev.filter((ds) => ds.id !== id));\n  };\n\n  return (\n    <div className=\"grid gap-4\">\n      <PageHeader\n        title=\"Datasets\"\n        description=\"Gevalideerde datasets met status, herkomst en versiebeheer.\"\n      />\n\n      <PageSection title=\"Filters\">\n        <div className=\"grid gap-3 sm:grid-cols-2\">\n          <div className=\"grid gap-1.5\">\n            <Label htmlFor=\"filter-vak\">Baggervak</Label>\n            <Input\n              id=\"filter-vak\"\n              placeholder=\"bijv. C5\"\n              value={baggervakFilter}\n              onChange={(e) => setBaggervakFilter(e.target.value)}\n            />\n          </div>\n          <div className=\"grid gap-1.5\">\n            <Label>Status</Label>\n            <Select value={statusFilter} onValueChange={setStatusFilter}>\n              <SelectTrigger>\n                <SelectValue placeholder=\"Alle\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\" \">Alle</SelectItem>\n                <SelectItem value=\"ready\">Gereed</SelectItem>\n                <SelectItem value=\"published\">Gepubliceerd</SelectItem>\n                <SelectItem value=\"processing\">Verwerken</SelectItem>\n                <SelectItem value=\"uploaded\">Geüpload</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n      </PageSection>\n\n      <PageSection title=\"Datasets\">\n        {loading ? (\n          <p className=\"text-sm text-muted-foreground\">Laden…</p>\n        ) : datasets.length === 0 ? (\n          <p className=\"text-sm text-muted-foreground\">Geen datasets gevonden.</p>\n        ) : (\n          <Table>\n            <TableHeader>\n              <TableRow>\n                <TableHead className=\"w-10\">#</TableHead>\n                <SortableHead\n                  label=\"Baggervak\"\n                  sortKey=\"baggervak_code\"\n                  activeKey={sortKey}\n                  activeDir={sortDir}\n                  onSort={handleSort}\n                />\n                <SortableHead\n                  label=\"Survey datum\"\n                  sortKey=\"survey_date\"\n                  activeKey={sortKey}\n                  activeDir={sortDir}\n                  onSort={handleSort}\n                />\n                <SortableHead\n                  label=\"Status\"\n                  sortKey=\"status\"\n                  activeKey={sortKey}\n                  activeDir={sortDir}\n                  onSort={handleSort}\n                />\n                <SortableHead\n                  label=\"Diepte (min/max)\"\n                  sortKey=\"depth_min\"\n                  activeKey={sortKey}\n                  activeDir={sortDir}\n                  onSort={handleSort}\n                />\n                <TableHead className=\"w-10\" />\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              {sortedDatasets.map((ds, index) => {\n                const st = ds.status ? STATUS_LABELS[ds.status] : STATUS_LABELS.uploaded;\n                return (\n                  <TableRow key={ds.id}>\n                    <TableCell className=\"text-muted-foreground\">{index + 1}</TableCell>\n                    <TableCell>\n                      <Link\n                        to={`/app/data/datasets/${ds.id}`}\n                        className=\"font-medium hover:underline\"\n                      >\n                        {ds.baggervak_code}\n                      </Link>\n                    </TableCell>\n                    <TableCell className=\"tabular-nums text-muted-foreground\">\n                      {ds.survey_date\n                        ? new Date(ds.survey_date + 'T00:00:00').toLocaleDateString('nl-NL')\n                        : '—'}\n                    </TableCell>\n                    <TableCell>\n                      <Badge variant={st.variant}>{st.label}</Badge>\n                    </TableCell>\n                    <TableCell className=\"tabular-nums\">\n                      {ds.depth_min != null && ds.depth_max != null\n                        ? `${ds.depth_min.toFixed(1)} / ${ds.depth_max.toFixed(1)} m`\n                        : '—'}\n                    </TableCell>\n                    <TableCell>\n                      <Button\n                        variant=\"ghost\"\n                        size=\"icon\"\n                        className=\"size-7 text-muted-foreground hover:text-destructive\"\n                        onClick={() => handleDelete(ds.id)}\n                      >\n                        <Trash2 className=\"size-3.5\" />\n                      </Button>\n                    </TableCell>\n                  </TableRow>\n                );\n              })}\n            </TableBody>\n          </Table>\n        )}\n      </PageSection>\n    </div>\n  );\n};\n\nexport default DataDatasets;\n","import { useEffect, useState } from 'react';\n\nimport type { ProcessingJob, ProcessingJobStatusEnum } from '@/js/api';\n\nimport { apiJobsList } from '@/js/api';\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/js/components/ui/table';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\nconst JOB_STATUS_LABELS: Record<\n  ProcessingJobStatusEnum,\n  { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' }\n> = {\n  pending: { label: 'Wachtrij', variant: 'secondary' },\n  running: { label: 'Bezig…', variant: 'default' },\n  success: { label: 'Voltooid', variant: 'outline' },\n  failure: { label: 'Mislukt', variant: 'destructive' },\n};\n\nconst JOB_TYPE_LABELS: Record<string, string> = {\n  extract: 'ZIP uitpakken',\n  convert: 'COG conversie',\n};\n\nconst DataJobs = () => {\n  const [jobs, setJobs] = useState<ProcessingJob[]>([]);\n  const [loading, setLoading] = useState(true);\n\n  const fetchJobs = async (showLoading = false) => {\n    if (showLoading) setLoading(true);\n    try {\n      const { data } = await apiJobsList({ query: { limit: 1000 } });\n      setJobs(data?.results ?? []);\n    } catch {\n      setJobs([]);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  useEffect(() => {\n    fetchJobs(true);\n    // Poll every 5 seconds for running jobs\n    const interval = setInterval(fetchJobs, 5000);\n    return () => clearInterval(interval);\n  }, []);\n\n  const hasRunning = jobs.some((j) => j.status === 'running' || j.status === 'pending');\n\n  return (\n    <div className=\"grid gap-4\">\n      <PageHeader\n        title=\"Verwerking\"\n        description=\"Wachtrij voor validatie, verwerking en publicatie van datasets.\"\n      >\n        {hasRunning && (\n          <span className=\"text-xs text-muted-foreground\">Ververst elke 5 seconden</span>\n        )}\n        <Button variant=\"outline\" size=\"sm\" onClick={() => fetchJobs()}>\n          Vernieuwen\n        </Button>\n      </PageHeader>\n\n      <PageSection title=\"Recente verwerkingen\">\n        {loading ? (\n          <p className=\"text-sm text-muted-foreground\">Laden…</p>\n        ) : jobs.length === 0 ? (\n          <p className=\"text-sm text-muted-foreground\">Geen verwerkingen gevonden.</p>\n        ) : (\n          <Table>\n            <TableHeader>\n              <TableRow>\n                <TableHead>ID</TableHead>\n                <TableHead>Type</TableHead>\n                <TableHead>Status</TableHead>\n                <TableHead>Voortgang</TableHead>\n                <TableHead>Peiling</TableHead>\n                <TableHead>Gestart</TableHead>\n                <TableHead>Foutmelding</TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              {jobs.map((job) => {\n                const st = JOB_STATUS_LABELS[job.status];\n                return (\n                  <TableRow key={job.id} className=\"align-top\">\n                    <TableCell className=\"font-mono text-xs\">{job.id}</TableCell>\n                    <TableCell>{JOB_TYPE_LABELS[job.job_type] ?? job.job_type}</TableCell>\n                    <TableCell>\n                      <Badge variant={st.variant}>{st.label}</Badge>\n                    </TableCell>\n                    <TableCell>\n                      <div className=\"flex items-center gap-2\">\n                        <div className=\"h-2 w-20 overflow-hidden rounded-full bg-secondary\">\n                          <div\n                            className=\"h-full rounded-full bg-primary transition-all\"\n                            style={{ width: `${job.progress_percent}%` }}\n                          />\n                        </div>\n                        <span className=\"text-xs tabular-nums text-muted-foreground\">\n                          {job.progress_percent}%\n                        </span>\n                      </div>\n                    </TableCell>\n                    <TableCell className=\"tabular-nums\">{job.survey}</TableCell>\n                    <TableCell className=\"text-muted-foreground\">\n                      {new Date(job.created).toLocaleString('nl-NL')}\n                    </TableCell>\n                    <TableCell className=\"max-w-md whitespace-pre-line break-words text-xs leading-relaxed text-destructive\">\n                      {job.error_message || '—'}\n                    </TableCell>\n                  </TableRow>\n                );\n              })}\n            </TableBody>\n          </Table>\n        )}\n      </PageSection>\n    </div>\n  );\n};\n\nexport default DataJobs;\n","import { useCallback, useRef, useState } from 'react';\n\nimport { apiSurveysConfirmUploadCreate, apiSurveysCreate } from '@/js/api';\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport { Input } from '@/js/components/ui/input';\nimport { Label } from '@/js/components/ui/label';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\ntype UploadState = 'idle' | 'uploading' | 'confirming' | 'done' | 'error';\n\ntype UploadEntry = {\n  file: File;\n  state: UploadState;\n  progress: number;\n  surveyId?: number;\n  jobId?: number;\n  error?: string;\n};\n\nfunction useUploadQueue() {\n  const [uploads, setUploads] = useState<UploadEntry[]>([]);\n\n  const updateUpload = (index: number, patch: Partial<UploadEntry>) => {\n    setUploads((prev) => prev.map((u, i) => (i === index ? { ...u, ...patch } : u)));\n  };\n\n  const addEntries = (files: FileList | null, startUpload: (file: File, index: number) => void) => {\n    if (!files) return;\n\n    const newEntries: UploadEntry[] = Array.from(files).map((file) => ({\n      file,\n      state: 'idle' as const,\n      progress: 0,\n    }));\n\n    setUploads((prev) => {\n      const startIndex = prev.length;\n      newEntries.forEach((entry, i) => {\n        startUpload(entry.file, startIndex + i);\n      });\n      return [...prev, ...newEntries];\n    });\n  };\n\n  const clearDone = () => {\n    setUploads((prev) => prev.filter((u) => u.state !== 'done' && u.state !== 'error'));\n  };\n\n  return { uploads, updateUpload, addEntries, clearDone };\n}\n\nfunction uploadToBlob(\n  file: File,\n  uploadUrl: string,\n  onProgress: (pct: number) => void\n): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const xhr = new XMLHttpRequest();\n    xhr.open('PUT', uploadUrl);\n    xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');\n    xhr.setRequestHeader('Content-Type', 'application/octet-stream');\n\n    xhr.upload.onprogress = (e) => {\n      if (e.lengthComputable) {\n        onProgress(Math.round((e.loaded / e.total) * 100));\n      }\n    };\n\n    xhr.onload = () =>\n      xhr.status >= 200 && xhr.status < 300\n        ? resolve()\n        : reject(new Error(`Upload failed: ${xhr.status}`));\n    xhr.onerror = () => reject(new Error('Network error during upload'));\n    xhr.send(file);\n  });\n}\n\nconst statusBadge = (state: UploadState) => {\n  const variants: Record<\n    UploadState,\n    { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' }\n  > = {\n    idle: { label: 'Wachtrij', variant: 'secondary' },\n    uploading: { label: 'Uploaden…', variant: 'default' },\n    confirming: { label: 'Verwerken…', variant: 'default' },\n    done: { label: 'Klaar', variant: 'outline' },\n    error: { label: 'Fout', variant: 'destructive' },\n  };\n  const { label, variant } = variants[state];\n  return <Badge variant={variant}>{label}</Badge>;\n};\n\nconst UploadQueue = ({ uploads, onClear }: { uploads: UploadEntry[]; onClear: () => void }) => {\n  if (uploads.length === 0) return null;\n\n  return (\n    <PageSection title=\"Wachtrij\">\n      <div className=\"grid gap-2\">\n        {uploads.map((entry, i) => (\n          <div key={i} className=\"flex items-center gap-3 rounded-lg border px-3 py-2 text-sm\">\n            <span className=\"min-w-0 flex-1 truncate\">{entry.file.name}</span>\n            <span className=\"text-xs tabular-nums text-muted-foreground\">{entry.progress}%</span>\n            {statusBadge(entry.state)}\n            {entry.error && <span className=\"text-xs text-destructive\">{entry.error}</span>}\n          </div>\n        ))}\n      </div>\n      <div className=\"mt-3\">\n        <Button variant=\"outline\" size=\"sm\" onClick={onClear}>\n          Wis voltooide\n        </Button>\n      </div>\n    </PageSection>\n  );\n};\n\nconst DropZone = ({\n  dragOver,\n  onDragOver,\n  onDragLeave,\n  onDrop,\n  onClick,\n  label,\n}: {\n  dragOver: boolean;\n  onDragOver: (e: React.DragEvent) => void;\n  onDragLeave: () => void;\n  onDrop: (e: React.DragEvent) => void;\n  onClick: () => void;\n  label: string;\n}) => (\n  <div\n    onDragOver={onDragOver}\n    onDragLeave={onDragLeave}\n    onDrop={onDrop}\n    onClick={onClick}\n    className={`flex min-h-32 cursor-pointer items-center justify-center rounded-lg border-2 border-dashed p-6 text-sm text-muted-foreground transition-colors ${\n      dragOver ? 'border-primary bg-gekozen' : 'border-border hover:border-primary/50'\n    }`}\n  >\n    {label}\n  </div>\n);\n\nconst DataUploads = () => {\n  const [source, setSource] = useState('Van den Herik');\n  const surveyQueue = useUploadQueue();\n  const [surveyDragOver, setSurveyDragOver] = useState(false);\n  const surveyFileRef = useRef<HTMLInputElement>(null);\n\n  const uploadSurveyFile = useCallback(\n    async (file: File, index: number) => {\n      try {\n        surveyQueue.updateUpload(index, { state: 'uploading', progress: 0 });\n\n        const { data } = await apiSurveysCreate({\n          body: { source, original_filename: file.name },\n        });\n        if (!data) throw new Error('Aanmaken survey mislukt');\n\n        const surveyId = data.id;\n        surveyQueue.updateUpload(index, { surveyId, progress: 10 });\n\n        await uploadToBlob(file, data.upload_url, (pct) => {\n          surveyQueue.updateUpload(index, { progress: 10 + Math.round(pct * 0.7) });\n        });\n\n        surveyQueue.updateUpload(index, { state: 'confirming', progress: 85 });\n\n        await apiSurveysConfirmUploadCreate({\n          path: { id: surveyId },\n          body: {},\n        });\n\n        surveyQueue.updateUpload(index, { state: 'done', progress: 100 });\n      } catch (err) {\n        const message = err instanceof Error ? err.message : 'Upload mislukt';\n        surveyQueue.updateUpload(index, { state: 'error', error: message });\n      }\n    },\n    [source]\n  );\n\n  const handleSurveyFiles = useCallback(\n    (files: FileList | null) => surveyQueue.addEntries(files, uploadSurveyFile),\n    [uploadSurveyFile]\n  );\n\n  const handleSurveyDrop = useCallback(\n    (e: React.DragEvent) => {\n      e.preventDefault();\n      setSurveyDragOver(false);\n      handleSurveyFiles(e.dataTransfer.files);\n    },\n    [handleSurveyFiles]\n  );\n\n  return (\n    <div className=\"grid gap-4\">\n      <PageHeader\n        title=\"Uploads\"\n        description=\"Peilingen en aantoonpeilingen aanleveren als ZIP-bestand.\"\n      />\n      <PageSection\n        title=\"Bestandsnamen\"\n        description=\"Een ZIP-bestand met XYZ-bestanden in de hoofdmap. De peildatum, het baggervak en de hoogtereferentie worden uit de bestandsnamen gehaald.\"\n      >\n        <ul className=\"list-disc space-y-1 pl-5 text-sm text-muted-foreground\">\n          <li>\n            Peilingen:{' '}\n            <code className=\"rounded bg-muted px-1 py-0.5 text-xs\">JJMMDD_BAGGERVAK_NAP.asc</code>{' '}\n            (bijv.{' '}\n            <code className=\"rounded bg-muted px-1 py-0.5 text-xs\">251201_D2_NAP.asc</code>)\n          </li>\n          <li>\n            Aantoonpeilingen:{' '}\n            <code className=\"rounded bg-muted px-1 py-0.5 text-xs\">JJMMDD_BAGGERVAK_ATP.asc</code>{' '}\n            (bijv.{' '}\n            <code className=\"rounded bg-muted px-1 py-0.5 text-xs\">251201_D2_ATP.asc</code>)\n          </li>\n        </ul>\n        <p className=\"mt-3 text-xs text-muted-foreground\">\n          Alleen NAP en ATP XYZ-bestanden worden geaccepteerd.\n        </p>\n      </PageSection>\n\n      <PageSection title=\"Gegevens bij de peiling\" description=\"Optioneel: pas de bron aan.\">\n        <div className=\"grid gap-1.5 max-w-sm\">\n          <Label htmlFor=\"source\">Bron</Label>\n          <Input\n            id=\"source\"\n            value={source}\n            onChange={(e) => setSource(e.target.value)}\n            placeholder=\"bijv. Van den Herik\"\n          />\n        </div>\n      </PageSection>\n\n      <PageSection\n        title=\"Peiling aanleveren\"\n        description=\"Sleep een ZIP-bestand hiernaartoe of klik om te selecteren.\"\n      >\n        <DropZone\n          dragOver={surveyDragOver}\n          onDragOver={(e) => {\n            e.preventDefault();\n            setSurveyDragOver(true);\n          }}\n          onDragLeave={() => setSurveyDragOver(false)}\n          onDrop={handleSurveyDrop}\n          onClick={() => surveyFileRef.current?.click()}\n          label=\"Sleep .zip bestanden hier of klik om te selecteren\"\n        />\n        <input\n          ref={surveyFileRef}\n          type=\"file\"\n          accept=\".zip\"\n          multiple\n          className=\"hidden\"\n          onChange={(e) => handleSurveyFiles(e.target.files)}\n        />\n      </PageSection>\n\n      <UploadQueue uploads={surveyQueue.uploads} onClear={surveyQueue.clearDone} />\n    </div>\n  );\n};\n\nexport default DataUploads;\n","import { useLocation } from 'react-router';\n\nimport PageHeader from '@/js/pages/PageHeader';\n\nconst Fase2ComingSoon = () => {\n  const location = useLocation();\n\n  return (\n    <PageHeader\n      title=\"Fase 2\"\n      description={`Deze module is gepland voor fase 2. Route: ${location.pathname}`}\n    />\n  );\n};\n\nexport default Fase2ComingSoon;\n","import { createContext, useContext, useReducer, type Dispatch, type ReactNode } from 'react';\n\nimport type { GebiedStatistiekResponse, PeilsoortEnum } from '@/js/api/types.gen';\nimport type { Colormap, KaartReferentie, ReferenceLevel } from '@/js/lib/depth-colors';\nimport type { Ring } from '@/js/lib/gebied';\nimport type { KaartObject } from '@/js/lib/kaart-object';\nimport type { OmgevingKaart } from '@/js/lib/omgeving';\nimport type { TijdlijnStatus } from '@/js/lib/tijdlijn-status';\n\n// ---------------------------------------------------------------------------\n// State\n// ---------------------------------------------------------------------------\n\nexport type Basemap = 'light' | 'standaard' | 'satellite';\n\nexport interface KaartState {\n  /**\n   * Zichtbare peilsoorten. Stond eerder als twee losse kaartlagen\n   * (`bathymetry` en `aantoonpeilingen`) in `layers`, wat verhulde dat het om\n   * één keuze gaat: welke soort peiling tel je mee. Zie #113.\n   */\n  peilsoorten: PeilsoortEnum[];\n  /** Layer visibility */\n  layers: {\n    /** Markers op de ondiepste plek per vak (#115). */\n    ondiepstePunten: boolean;\n    /**\n     * Dieptecijfers op de kaart (#114). Standaard uit: elke peiling kost aan\n     * de serverkant een blob-download, en dat is te duur om ongevraagd te doen.\n     */\n    dieptecijfers: boolean;\n    /**\n     * Hoogtelijnen op de kaart (#114). Standaard uit, en om dezelfde reden als\n     * de cijfers: het kost aan de serverkant een blob-download per peiling.\n     * Werkt alleen tegen een peil (NAP, LAT).\n     */\n    dieptecontouren: boolean;\n    baggervakken: boolean;\n    vaargeul: boolean;\n    randstroken: boolean;\n    passeerstroken: boolean;\n    taluds: boolean;\n    stortlocaties: boolean;\n    /**\n     * Vaartracks van de hoppers over het venster tussen de vorige en de\n     * geselecteerde peiling (#WP5). Standaard uit: de laag kost een request\n     * per datumstap en is niet voor iedereen elke sessie relevant.\n     */\n    scheepsactiviteit: boolean;\n    /**\n     * Omgeving actueel: de bodem rond de vakken, per maand bijgewerkt (ontwerp\n     * 2026-10-14). Standaard aan; de herkomst staat bij de speld na een klik.\n     */\n    omgeving: boolean;\n    /**\n     * Schaduwlaag over de dieptekleuren, zodat duinen en baggersporen zichtbaar\n     * worden en met de tijdslider meelopen (WP6). Standaard uit: elke tegel\n     * wordt in de browser uitgerekend, naast de dieptetegel.\n     */\n    relief: boolean;\n  };\n  /**\n   * Waarom een laag niets laat zien. Leeg betekent \"niets te melden\" en niet\n   * \"geen data\" — die twee uit elkaar houden is het hele punt.\n   */\n  laagfouten: {\n    scheepsactiviteit: string | null;\n  };\n  /**\n   * Waarom een laag leeg is zonder dat er iets mis is: de AIS-aanlevering\n   * reikt niet tot deze maand, of er is niet gevaren. Geen fout, dus niet in\n   * rood, maar wel het verschil tussen \"niets te zien\" en \"kapot\".\n   *\n   * Ook wat een laag nog aan het doen is (cijfers en contouren worden op de\n   * server berekend) en waar hij minder zegt dan hij lijkt (vakken zonder\n   * referentieraster). MapView weet het, het werkblad zet het onder de laag.\n   */\n  laagmeldingen: {\n    scheepsactiviteit: string | null;\n    dieptecijfers: string | null;\n    dieptecontouren: string | null;\n  };\n  /** Active basemap */\n  basemap: Basemap;\n  /** Active reference level for color mapping; nooit IVD, zie {@link kaartReferentie}. */\n  referenceLevel: KaartReferentie;\n  /**\n   * Klassen of verloop bij een ontwerpdiepte (#114).\n   *\n   * Blijft staan als je naar een peil wisselt, ook al valt er daar niets te\n   * kiezen: ga je terug naar een ontwerpdiepte, dan sta je weer op de schaal\n   * die je gekozen had.\n   */\n  colormap: Colormap;\n  /** Currently selected baggervak code (click) */\n  selectedVak: string | null;\n  /**\n   * Waar precies op geklikt is: het vak zelf, of een strook, talud of\n   * stortlocatie. `selectedVak` is dan het vak waar die vorm bij hoort.\n   */\n  selectie: KaartObject | null;\n  /** Selected survey date (ISO string) */\n  selectedDate: string | null;\n  /** Available survey dates */\n  availableDates: string[];\n  /** Retention venster in maanden (0 = onbeperkt) */\n  retentionMonths: number;\n  /** Clip bathymetry to baggervak polygons */\n  clipToBaggervakken: boolean;\n  /** 3D mode */\n  is3D: boolean;\n  /** De omgevingskaarten uit `/api/omgeving/` (MapView haalt ze op; legenda en tijdlijn lezen mee). */\n  omgevingKaarten: OmgevingKaart[];\n  /** Cross-section mode */\n  crossSection: {\n    drawing: boolean;\n    line: [number, number][] | null;\n  };\n  /**\n   * Zelfgetekend meetgebied (#115). De uitkomst hoort hier en niet in het\n   * paneel, omdat de kaart hem ook nodig heeft: het ondiepste punt van het\n   * gebied wordt als marker getekend.\n   */\n  gebied: {\n    drawing: boolean;\n    ring: Ring | null;\n    bezig: boolean;\n    resultaat: GebiedStatistiekResponse | null;\n    fout: string | null;\n  };\n  /**\n   * Wat het wisselmechanisme heeft klaarstaan (use-maand-lagen): de kaart\n   * meldt het hier, de tijdlijnbalk toont het als stipjes + laad-indicator.\n   * Puur weergave — niets stuurt hierop.\n   */\n  tijdlijn: TijdlijnStatus;\n}\n\nconst initialState: KaartState = {\n  peilsoorten: ['regulier'],\n  layers: {\n    ondiepstePunten: true,\n    dieptecijfers: false,\n    dieptecontouren: false,\n    baggervakken: true,\n    vaargeul: false,\n    randstroken: true,\n    passeerstroken: true,\n    taluds: false,\n    stortlocaties: true,\n    scheepsactiviteit: false,\n    omgeving: true,\n    relief: false,\n  },\n  laagfouten: {\n    scheepsactiviteit: null,\n  },\n  laagmeldingen: {\n    scheepsactiviteit: null,\n    dieptecijfers: null,\n    dieptecontouren: null,\n  },\n  basemap: 'light',\n  referenceLevel: 'ngd',\n  colormap: 'continu',\n  selectedVak: null,\n  selectie: null,\n  selectedDate: null,\n  availableDates: [],\n  retentionMonths: 1,\n  clipToBaggervakken: false,\n  is3D: false,\n  omgevingKaarten: [],\n  crossSection: { drawing: false, line: null },\n  gebied: { drawing: false, ring: null, bezig: false, resultaat: null, fout: null },\n  tijdlijn: {\n    geladenDatums: [],\n    ladendeDatums: [],\n    geselecteerdBezig: false,\n    achtergrondBezig: false,\n    fout: null,\n  },\n};\n\n/**\n * IVD komt in het bestek 2026 niet meer voor en de kaart biedt hem niet meer\n * aan. Een oude keuze (een opgeslagen voorkeur, een link, een oude test) valt\n * terug op NGD, de standaard, in plaats van een kaart zonder knop te tonen.\n */\nexport function kaartReferentie(level: ReferenceLevel): KaartReferentie {\n  return level === 'ivd' ? 'ngd' : level;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\nexport type KaartAction =\n  | { type: 'TOGGLE_LAYER'; layer: keyof KaartState['layers'] }\n  | { type: 'SET_LAAGFOUT'; laag: 'scheepsactiviteit'; fout: string | null }\n  | {\n      type: 'SET_LAAGMELDING';\n      laag: keyof KaartState['laagmeldingen'];\n      melding: string | null;\n    }\n  | { type: 'TOGGLE_PEILSOORT'; peilsoort: PeilsoortEnum }\n  | { type: 'SET_BASEMAP'; basemap: Basemap }\n  | { type: 'SET_REFERENCE_LEVEL'; level: ReferenceLevel }\n  | { type: 'SET_COLORMAP'; colormap: Colormap }\n  | { type: 'SELECT_VAK'; code: string | null }\n  | { type: 'SELECT_OBJECT'; object: KaartObject | null }\n  | { type: 'SET_DATE'; date: string | null }\n  | { type: 'SET_AVAILABLE_DATES'; dates: string[] }\n  | ({ type: 'SET_TIJDLIJN_STATUS' } & TijdlijnStatus)\n  | { type: 'SET_RETENTION_MONTHS'; months: number }\n  | { type: 'TOGGLE_CLIP' }\n  | { type: 'TOGGLE_3D' }\n  | { type: 'SET_OMGEVING_KAARTEN'; kaarten: OmgevingKaart[] }\n  | { type: 'START_CROSS_SECTION' }\n  | { type: 'SET_CROSS_SECTION_LINE'; line: [number, number][] | null }\n  | { type: 'CANCEL_CROSS_SECTION' }\n  | { type: 'START_GEBIED' }\n  | { type: 'SET_GEBIED_RING'; ring: Ring }\n  | { type: 'GEBIED_BEZIG' }\n  | { type: 'GEBIED_RESULTAAT'; resultaat: GebiedStatistiekResponse }\n  | { type: 'GEBIED_FOUT'; fout: string }\n  | { type: 'CANCEL_GEBIED' };\n\nfunction reducer(state: KaartState, action: KaartAction): KaartState {\n  switch (action.type) {\n    case 'TOGGLE_LAYER':\n      return {\n        ...state,\n        layers: {\n          ...state.layers,\n          [action.layer]: !state.layers[action.layer],\n        },\n      };\n    case 'SET_LAAGFOUT':\n      return {\n        ...state,\n        laagfouten: { ...state.laagfouten, [action.laag]: action.fout },\n      };\n    case 'SET_LAAGMELDING':\n      return {\n        ...state,\n        laagmeldingen: { ...state.laagmeldingen, [action.laag]: action.melding },\n      };\n    case 'TOGGLE_PEILSOORT':\n      return {\n        ...state,\n        peilsoorten: state.peilsoorten.includes(action.peilsoort)\n          ? state.peilsoorten.filter((s) => s !== action.peilsoort)\n          : [...state.peilsoorten, action.peilsoort],\n      };\n    case 'SET_BASEMAP':\n      return { ...state, basemap: action.basemap };\n    case 'SET_REFERENCE_LEVEL':\n      return { ...state, referenceLevel: kaartReferentie(action.level) };\n    case 'SET_COLORMAP':\n      return { ...state, colormap: action.colormap };\n    case 'SELECT_VAK':\n      return { ...state, selectedVak: action.code, selectie: null };\n    case 'SELECT_OBJECT':\n      return { ...state, selectedVak: action.object?.vak ?? null, selectie: action.object };\n    case 'SET_DATE':\n      return { ...state, selectedDate: action.date };\n    case 'SET_AVAILABLE_DATES':\n      return { ...state, availableDates: action.dates };\n    case 'SET_TIJDLIJN_STATUS':\n      return {\n        ...state,\n        tijdlijn: {\n          geladenDatums: action.geladenDatums,\n          ladendeDatums: action.ladendeDatums,\n          geselecteerdBezig: action.geselecteerdBezig,\n          achtergrondBezig: action.achtergrondBezig,\n          fout: action.fout,\n        },\n      };\n    case 'SET_RETENTION_MONTHS':\n      return { ...state, retentionMonths: action.months };\n    case 'TOGGLE_CLIP':\n      return { ...state, clipToBaggervakken: !state.clipToBaggervakken };\n    case 'TOGGLE_3D':\n      return { ...state, is3D: !state.is3D };\n    case 'SET_OMGEVING_KAARTEN':\n      return { ...state, omgevingKaarten: action.kaarten };\n    case 'START_CROSS_SECTION':\n      // Beide gereedschappen luisteren naar dezelfde muisklik, dus het ene\n      // aanzetten moet het andere uitzetten. Anders zet een klik een hoekpunt\n      // en een dwarsprofielpunt tegelijk.\n      return {\n        ...state,\n        crossSection: { drawing: true, line: null },\n        gebied: { ...state.gebied, drawing: false },\n      };\n    case 'SET_CROSS_SECTION_LINE':\n      return {\n        ...state,\n        crossSection: { drawing: false, line: action.line },\n      };\n    case 'CANCEL_CROSS_SECTION':\n      return {\n        ...state,\n        crossSection: { drawing: false, line: null },\n      };\n    case 'START_GEBIED':\n      return {\n        ...state,\n        crossSection: { ...state.crossSection, drawing: false },\n        gebied: { drawing: true, ring: null, bezig: false, resultaat: null, fout: null },\n      };\n    case 'SET_GEBIED_RING':\n      return {\n        ...state,\n        gebied: {\n          drawing: false,\n          ring: action.ring,\n          bezig: true,\n          resultaat: null,\n          fout: null,\n        },\n      };\n    case 'GEBIED_BEZIG':\n      return { ...state, gebied: { ...state.gebied, bezig: true, fout: null } };\n    case 'GEBIED_RESULTAAT':\n      return {\n        ...state,\n        gebied: { ...state.gebied, bezig: false, resultaat: action.resultaat, fout: null },\n      };\n    case 'GEBIED_FOUT':\n      return {\n        ...state,\n        gebied: { ...state.gebied, bezig: false, resultaat: null, fout: action.fout },\n      };\n    case 'CANCEL_GEBIED':\n      return {\n        ...state,\n        gebied: { drawing: false, ring: null, bezig: false, resultaat: null, fout: null },\n      };\n    default:\n      return state;\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface KaartContextValue {\n  state: KaartState;\n  dispatch: Dispatch<KaartAction>;\n}\n\nconst KaartContext = createContext<KaartContextValue | null>(null);\n\nexport function KaartProvider({ children }: { children: ReactNode }) {\n  const [state, dispatch] = useReducer(reducer, initialState);\n  return <KaartContext.Provider value={{ state, dispatch }}>{children}</KaartContext.Provider>;\n}\n\nexport function useKaart() {\n  const ctx = useContext(KaartContext);\n  if (!ctx) throw new Error('useKaart must be used within KaartProvider');\n  return ctx;\n}\n\n/** Of een peilsoort op dit moment op de kaart getoond wordt. */\nexport function toontPeilsoort(state: KaartState, peilsoort: PeilsoortEnum): boolean {\n  return state.peilsoorten.includes(peilsoort);\n}\n","import { useEffect, useState } from 'react';\n\nimport type { ApiOverlaysListData } from '@/js/api';\n\nimport { apiOverlaysList } from '@/js/api';\nimport { transformGeoJSON } from '@/js/lib/projections';\n\ntype OverlayLayerType = NonNullable<NonNullable<ApiOverlaysListData['query']>['layer_type']>;\n\nexport function useOverlays(layerType: OverlayLayerType) {\n  const [geojson, setGeojson] = useState<GeoJSON.FeatureCollection | null>(null);\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    let cancelled = false;\n\n    const fetch = async () => {\n      try {\n        const { data } = await apiOverlaysList({\n          query: { layer_type: layerType, limit: 200 },\n        });\n        if (cancelled) return;\n\n        const fc = data?.results;\n        if (!fc?.features?.length) {\n          setGeojson(null);\n          return;\n        }\n\n        const transformed = transformGeoJSON(fc as unknown as GeoJSON.FeatureCollection);\n        setGeojson(transformed);\n      } catch {\n        if (!cancelled) setError(`Overlays (${layerType}) konden niet worden geladen.`);\n      } finally {\n        if (!cancelled) setLoading(false);\n      }\n    };\n\n    fetch();\n    return () => {\n      cancelled = true;\n    };\n  }, [layerType]);\n\n  return { geojson, loading, error };\n}\n","/**\n * Sub 7 (#97): per-baggervak reference-depth COG fetcher.\n *\n * Haalt een tijdelijk-beperkte SAS URL op voor de server-side gegenereerde\n * reference-depth COG. Deze wordt door {@link renderDeltaTile} gebruikt om\n * per pixel de lokale referentiediepte af te trekken van de bathymetrie.\n *\n * De openapi-ts generator ziet het `reference-cog-url` endpoint pas na een\n * schema-regeneratie (pre-commit hook). Tot die tijd roepen we rechtstreeks\n * via de shared HTTP-client aan — zelfde CSRF/cookie-handling als de\n * gegenereerde SDK.\n */\n\nimport { client } from '@/js/api/client.gen';\nimport { meet, tel } from '@/js/lib/tijdlijn-meting';\n\n/** URL-cache — keyed by `${baggervakCode}|${level}`. Refresh voor 50min-TTL. */\nconst cache = new Map<string, { url: string | null; fetchedAt: number }>();\nconst inFlight = new Map<string, Promise<string | null>>();\nconst TTL_MS = 50 * 60 * 1000;\n\nfunction cacheKey(baggervakCode: string, level: string): string {\n  return `${baggervakCode}|${level}`;\n}\n\n/**\n * Resolve de SAS URL van de reference-COG voor een baggervak × referentielevel.\n *\n * Retourneert ``null`` als de server nog geen COG heeft gegenereerd (404) of\n * bij netwerkfout. De caller valt dan terug op het legacy scalar-NGD pad.\n */\nexport async function getReferenceCogUrl(\n  baggervakCode: string,\n  level: 'ngd' | 'ivd' | 'ohd' = 'ngd'\n): Promise<string | null> {\n  const key = cacheKey(baggervakCode, level);\n  const cached = cache.get(key);\n  if (cached && Date.now() - cached.fetchedAt < TTL_MS) {\n    tel('referentie URL uit cache');\n    return cached.url;\n  }\n  const existing = inFlight.get(key);\n  if (existing) return existing;\n\n  const promise = meet(\n    'api.referentie-url',\n    () =>\n      client.get<{ url: string; level: string }>({\n        url: `/api/baggervakken/${encodeURIComponent(baggervakCode)}/reference-cog-url/`,\n        query: { level },\n        security: [{ in: 'cookie', name: 'sessionid', type: 'apiKey' }],\n        responseType: 'json',\n      }),\n    { vak: baggervakCode, level }\n  )\n    .then(({ data }) => {\n      const url = (data as unknown as { url?: string })?.url ?? null;\n      cache.set(key, { url, fetchedAt: Date.now() });\n      return url;\n    })\n    .catch(() => {\n      // 404 (reference-COG nog niet gegenereerd) of netwerkfout → null.\n      // Cache'en zodat we niet bij elke tile-fetch opnieuw retry'en.\n      cache.set(key, { url: null, fetchedAt: Date.now() });\n      return null;\n    })\n    .finally(() => inFlight.delete(key));\n\n  inFlight.set(key, promise);\n  return promise;\n}\n\nexport function clearReferenceCogUrlCache(): void {\n  cache.clear();\n  inFlight.clear();\n}\n","import { locationValues } from '@geomatico/maplibre-cog-protocol';\n\nimport { getReferenceCogUrl } from '@/js/hooks/use-reference-cog';\n\nexport interface CrossSectionPoint {\n  distance: number;\n  depth: number | null;\n}\n\nexport interface CrossSectionSeries {\n  date: string;\n  points: CrossSectionPoint[];\n}\n\n/**\n * Sample depth values along a line at regular intervals.\n * Uses locationValues() from maplibre-cog-protocol to read COG pixel values.\n *\n * @param cogUrl - The COG URL (without cog:// prefix)\n * @param line - Array of [lng, lat] coordinates defining the line\n * @param intervalM - Sample interval in meters (approximate)\n * @param zoom - Map zoom level for sampling resolution\n */\nexport async function sampleCrossSection(\n  cogUrl: string,\n  line: [number, number][],\n  intervalM = 5,\n  zoom = 14\n): Promise<CrossSectionPoint[]> {\n  if (line.length < 2) return [];\n\n  const [startLng, startLat] = line[0];\n  const [endLng, endLat] = line[1];\n\n  // Approximate distance in meters using haversine\n  const totalDistance = haversineDistance(startLat, startLng, endLat, endLng);\n  const numSamples = Math.max(2, Math.ceil(totalDistance / intervalM));\n\n  const points: CrossSectionPoint[] = [];\n\n  for (let i = 0; i <= numSamples; i++) {\n    const t = i / numSamples;\n    const lng = startLng + t * (endLng - startLng);\n    const lat = startLat + t * (endLat - startLat);\n    const distance = t * totalDistance;\n\n    try {\n      const values = await locationValues(cogUrl, { latitude: lat, longitude: lng }, zoom);\n      const raw = values.length > 0 ? values[0] : null;\n      points.push({\n        distance: Math.round(distance * 10) / 10,\n        depth: raw != null && Number.isFinite(raw) ? raw : null,\n      });\n    } catch {\n      points.push({ distance: Math.round(distance * 10) / 10, depth: null });\n    }\n  }\n\n  return points;\n}\n\n/**\n * Sample the same line against multiple COGs (for multi-date comparison).\n */\nexport async function sampleMultiDate(\n  cogEntries: { date: string; url: string }[],\n  line: [number, number][],\n  intervalM = 5,\n  zoom = 14\n): Promise<CrossSectionSeries[]> {\n  const results: CrossSectionSeries[] = [];\n\n  for (const { date, url } of cogEntries) {\n    const points = await sampleCrossSection(url, line, intervalM, zoom);\n    results.push({ date, points });\n  }\n\n  return results;\n}\n\n/**\n * Ray-casting point-in-polygon test.\n * Works with a single polygon ring (array of [x,y] coordinates).\n */\nfunction pointInRing(x: number, y: number, ring: number[][]): boolean {\n  let inside = false;\n  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n    const xi = ring[i][0],\n      yi = ring[i][1];\n    const xj = ring[j][0],\n      yj = ring[j][1];\n    if (yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) {\n      inside = !inside;\n    }\n  }\n  return inside;\n}\n\n/**\n * Test whether a point [lng, lat] falls inside a GeoJSON feature's geometry.\n * Supports Polygon and MultiPolygon (ignores holes for simplicity).\n */\nexport function pointInFeature(lng: number, lat: number, feature: GeoJSON.Feature): boolean {\n  const geom = feature.geometry;\n  if (geom.type === 'Polygon') {\n    return pointInRing(lng, lat, geom.coordinates[0]);\n  }\n  if (geom.type === 'MultiPolygon') {\n    return geom.coordinates.some((poly) => pointInRing(lng, lat, poly[0]));\n  }\n  return false;\n}\n\nexport function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {\n  const R = 6371000; // Earth radius in meters\n  const dLat = ((lat2 - lat1) * Math.PI) / 180;\n  const dLon = ((lon2 - lon1) * Math.PI) / 180;\n  const a =\n    Math.sin(dLat / 2) ** 2 +\n    Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;\n  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n}\n\nexport interface ReferenceSampleInput {\n  lng: number;\n  lat: number;\n}\n\nexport interface ReferenceSampleResult {\n  ngd: number | null;\n  ivd: number | null;\n  ohd: number | null;\n}\n\nconst LEVELS = ['ngd', 'ivd', 'ohd'] as const;\ntype Level = (typeof LEVELS)[number];\n\n/**\n * Per-sample resolution of a reference-COG lookup:\n * - `cogCode`: which baggervak's reference-COG to query (`null` → no COG fetch).\n * - `scalar`: scalar fallback for `{level}` if the COG is unavailable or returns NaN.\n *\n * The two paths use different property shapes:\n *   - Baggervak feature  → `properties.code`            + `properties.{level}_level`\n *   - Overlay feature    → `properties.properties.baggervak` + `properties.properties.{level}`\n */\ntype ResolvedSample = {\n  cogCode: string | null;\n  scalars: Record<Level, number | null>;\n};\n\nfunction resolveBaggervak(feature: GeoJSON.Feature): ResolvedSample {\n  const code = feature.properties?.code;\n  const props = feature.properties ?? {};\n  return {\n    cogCode: typeof code === 'string' ? code : null,\n    scalars: {\n      ngd: typeof props.ngd_level === 'number' ? props.ngd_level : null,\n      ivd: typeof props.ivd_level === 'number' ? props.ivd_level : null,\n      ohd: typeof props.ohd_level === 'number' ? props.ohd_level : null,\n    },\n  };\n}\n\nfunction resolveOverlay(feature: GeoJSON.Feature): ResolvedSample {\n  // OverlaySerializer (drf-gis GeoFeatureModelSerializer) nest de model's eigen\n  // JSONField onder `properties.properties` — zie `backend/surveys/serializers.py`\n  // (OverlaySerializer) en `test_overlay_serializer.py`.\n  const inner = (feature.properties?.properties as Record<string, unknown> | undefined) ?? {};\n  const baggervak = inner.baggervak;\n  return {\n    cogCode: typeof baggervak === 'string' ? baggervak : null,\n    scalars: {\n      ngd: typeof inner.ngd === 'number' ? inner.ngd : null,\n      ivd: typeof inner.ivd === 'number' ? inner.ivd : null,\n      ohd: typeof inner.ohd === 'number' ? inner.ohd : null,\n    },\n  };\n}\n\n/**\n * Sample NGD / IVD / OHD reference depths per point along a cross-section line.\n *\n * Voor elk sample-point wordt eerst gezocht naar het bevattende baggervak; als\n * dat mist (passeerstroken/taluds liggen *naast* de baggervak — zie\n * `backend/surveys/reference_cog.py:215`), valt de helper terug op de\n * `overlays` collectie en gebruikt `properties.properties.baggervak` om de\n * juiste reference-COG te vinden.\n *\n * Vervolgens wordt — één keer per baggervak-code × level — via\n * `getReferenceCogUrl` de SAS-URL opgehaald. Als die beschikbaar is lezen we\n * per sample de lokale waarde met `locationValues()`; anders vallen we terug\n * op de scalar properties (legacy pad).\n */\nexport async function sampleReferenceLevelsAlongLine(\n  samples: ReferenceSampleInput[],\n  baggervakken: GeoJSON.FeatureCollection | null,\n  overlays: GeoJSON.FeatureCollection | null = null,\n  zoom = 14\n): Promise<ReferenceSampleResult[]> {\n  const resolved: (ResolvedSample | null)[] = samples.map(({ lng, lat }) => {\n    if (baggervakken) {\n      for (const feature of baggervakken.features) {\n        if (pointInFeature(lng, lat, feature)) return resolveBaggervak(feature);\n      }\n    }\n    if (overlays) {\n      for (const feature of overlays.features) {\n        if (pointInFeature(lng, lat, feature)) return resolveOverlay(feature);\n      }\n    }\n    return null;\n  });\n\n  const codes = new Set<string>();\n  for (const r of resolved) {\n    if (r?.cogCode) codes.add(r.cogCode);\n  }\n\n  const urlByCodeLevel = new Map<string, string | null>();\n  await Promise.all(\n    Array.from(codes).flatMap((code) =>\n      LEVELS.map(async (level) => {\n        const url = await getReferenceCogUrl(code, level);\n        urlByCodeLevel.set(`${code}|${level}`, url);\n      })\n    )\n  );\n\n  const perLevelValues: Record<Level, (number | null)[]> = {\n    ngd: new Array(samples.length).fill(null),\n    ivd: new Array(samples.length).fill(null),\n    ohd: new Array(samples.length).fill(null),\n  };\n\n  await Promise.all(\n    samples.flatMap((pt, idx) => {\n      const r = resolved[idx];\n      if (!r) return [];\n\n      return LEVELS.map(async (level) => {\n        const scalar = r.scalars[level];\n        const url = r.cogCode ? (urlByCodeLevel.get(`${r.cogCode}|${level}`) ?? null) : null;\n\n        if (!url) {\n          perLevelValues[level][idx] = scalar;\n          return;\n        }\n\n        try {\n          const values = await locationValues(url, { latitude: pt.lat, longitude: pt.lng }, zoom);\n          const raw = values.length > 0 ? values[0] : null;\n          if (raw != null && Number.isFinite(raw)) {\n            perLevelValues[level][idx] = raw;\n          } else {\n            perLevelValues[level][idx] = scalar;\n          }\n        } catch {\n          perLevelValues[level][idx] = scalar;\n        }\n      });\n    })\n  );\n\n  return samples.map((_, idx) => ({\n    ngd: perLevelValues.ngd[idx],\n    ivd: perLevelValues.ivd[idx],\n    ohd: perLevelValues.ohd[idx],\n  }));\n}\n","import { RotateCcw, Scissors, X } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  Area,\n  CartesianGrid,\n  ComposedChart,\n  Customized,\n  Line,\n  ReferenceArea,\n  ReferenceLine,\n  XAxis,\n  YAxis,\n} from 'recharts';\n\nimport { useKaart, toontPeilsoort } from './KaartContext';\n\nimport type { SurveyDataset } from '@/js/api';\nimport type { MapRef } from 'react-map-gl/maplibre';\n\nimport { PanamaxHull, PANAMAX_FREEBOARD } from '@/js/components/PanamaxHull';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  type ChartConfig,\n} from '@/js/components/ui/chart';\nimport { getCogUrl } from '@/js/hooks/use-datasets';\nimport { useOverlays } from '@/js/hooks/use-overlays';\nimport {\n  haversineDistance,\n  sampleCrossSection,\n  sampleReferenceLevelsAlongLine,\n  type CrossSectionPoint,\n  type ReferenceSampleInput,\n} from '@/js/lib/cross-section';\nimport { mergeAtpPriority } from '@/js/lib/cross-section-merge';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { REF_LEVEL_COLORS } from '@/js/lib/reference-levels';\n\nconst DEFAULT_HEIGHT = 280;\nconst MIN_HEIGHT = 150;\n/** Zoveel kaart blijft er boven de lade altijd over. */\nconst MIN_KAART = 160;\nconst TOETS_STAP = 24;\n\n/**\n * Recharts-adapter: pakt xAxis/yAxis-scales uit `<Customized>` props en geeft\n * ze door als plain callbacks aan het herbruikbare `<PanamaxHull>`.\n */\nfunction PanamaxHullRechartsAdapter(props: any) {\n  const xAxisMap = props.xAxisMap ?? {};\n  const yAxisMap = props.yAxisMap ?? {};\n  const xAxis = xAxisMap[0] ?? Object.values(xAxisMap)[0];\n  const yAxis = yAxisMap[0] ?? Object.values(yAxisMap)[0];\n  if (!xAxis?.scale || !yAxis?.scale) return null;\n\n  const xScale = xAxis.scale;\n  const yScale = yAxis.scale;\n  const [xMin, xMax] = xScale.domain();\n  const cx = (xMin + xMax) / 2;\n\n  return (\n    <PanamaxHull\n      xScale={(x) => xScale(x) as number}\n      yScale={(y) => yScale(y) as number}\n      centerX={cx}\n    />\n  );\n}\n\ninterface ChartPoint extends CrossSectionPoint {\n  ngd?: number | null;\n  ivd?: number | null;\n  ohd?: number | null;\n  atpDepth?: number | null;\n}\n\ninterface CrossSectionDrawerProps {\n  datasets: SurveyDataset[];\n  atpDatasets: SurveyDataset[];\n  baggervakken: GeoJSON.FeatureCollection | null;\n  /** De kaart krimpt mee met de lade, en kent zijn nieuwe hoogte pas na resize(). */\n  mapRef: React.RefObject<MapRef | null>;\n}\n\nconst chartConfig: ChartConfig = {\n  depth: {\n    label: 'Diepte (m NAP)',\n    color: '#6B5B3E',\n  },\n  atpDepth: {\n    label: 'Aantoonpeiling (m NAP)',\n    color: '#d946ef',\n  },\n  ngd: {\n    label: 'NGD',\n    color: '#ef4444',\n  },\n  // OHD is de maximale waterbodemdiepte uit het bestek 2026. IVD komt daar niet\n  // meer in voor en wordt niet meer getekend.\n  ohd: {\n    label: 'max.',\n    color: '#8b5cf6',\n  },\n};\n\nexport default function CrossSectionDrawer({\n  datasets,\n  atpDatasets,\n  baggervakken,\n  mapRef,\n}: CrossSectionDrawerProps) {\n  const { state, dispatch } = useKaart();\n  const [depthData, setDepthData] = useState<ChartPoint[]>([]);\n  const [loading, setLoading] = useState(false);\n\n  // Een lade onderaan de kaart in plaats van een zwevend paneel: het profiel\n  // ligt dan nooit over het stuk geul waar de lijn getekend is. Alleen de\n  // hoogte is te verslepen, aan de bovenrand.\n  const [height, setHeight] = useState(DEFAULT_HEIGHT);\n  const ladeRef = useRef<HTMLElement>(null);\n  const sleep = useRef<{ y: number; height: number } | null>(null);\n  // Hoogte van de kaartkolom; 0 zolang die niet gemeten is (jsdom).\n  const [kolom, setKolom] = useState(0);\n  const maxHoogte = kolom > 0 ? Math.max(MIN_HEIGHT, kolom - MIN_KAART) : null;\n\n  const begrens = useCallback((h: number) => {\n    const kolomNu = ladeRef.current?.parentElement?.clientHeight ?? 0;\n    const max = kolomNu > 0 ? Math.max(MIN_HEIGHT, kolomNu - MIN_KAART) : Infinity;\n    return Math.round(Math.min(max, Math.max(MIN_HEIGHT, h)));\n  }, []);\n\n  const heeftLijn = !!state.crossSection.line;\n  // Ook als het venster lager wordt (of de devtools opengaan) moet er kaart\n  // overblijven: een eerder hoog gesleepte lade krimpt dan mee.\n  useEffect(() => {\n    const ouder = ladeRef.current?.parentElement;\n    if (!ouder) return;\n    const meet = () => {\n      setKolom(ouder.clientHeight);\n      setHeight((h) => begrens(h));\n    };\n    meet();\n    const waarnemer = new ResizeObserver(meet);\n    waarnemer.observe(ouder);\n    return () => waarnemer.disconnect();\n  }, [begrens, heeftLijn]);\n\n  const onSleepStart = useCallback(\n    (e: React.PointerEvent) => {\n      e.preventDefault();\n      sleep.current = { y: e.clientY, height };\n      (e.target as HTMLElement).setPointerCapture?.(e.pointerId);\n    },\n    [height]\n  );\n  const onSleep = useCallback(\n    (e: React.PointerEvent) => {\n      if (!sleep.current) return;\n      setHeight(begrens(sleep.current.height + sleep.current.y - e.clientY));\n    },\n    [begrens]\n  );\n  const onSleepEinde = useCallback(() => {\n    sleep.current = null;\n  }, []);\n\n  // De kaart staat boven de lade en wordt kleiner of groter; zonder resize()\n  // blijft MapLibre op de oude hoogte tekenen. Eén keer per frame is genoeg,\n  // ook tijdens het slepen.\n  useEffect(() => {\n    const frame = requestAnimationFrame(() => mapRef.current?.resize());\n    return () => cancelAnimationFrame(frame);\n  }, [height, mapRef]);\n  useEffect(\n    () => () => {\n      requestAnimationFrame(() => mapRef.current?.resize());\n    },\n    [mapRef]\n  );\n\n  // De reference-COG is per baggervak gedekt, maar passeerstroken/taluds\n  // (en soms randstroken) liggen geografisch *naast* de baggervak — niet\n  // erbinnen. We laden ze hier expliciet om voor sample-points die buiten\n  // elk baggervak vallen alsnog de juiste parent-COG te kunnen vinden via\n  // `properties.properties.baggervak` (zie `sampleReferenceLevelsAlongLine`).\n  const { geojson: vaargeulOverlays } = useOverlays('vaargeul');\n  const { geojson: passeerstrookOverlays } = useOverlays('passeerstrook');\n  const { geojson: randstrookOverlays } = useOverlays('randstrook');\n  const { geojson: taludOverlays } = useOverlays('talud');\n\n  const depthOverlays = useMemo<GeoJSON.FeatureCollection | null>(() => {\n    const collections = [\n      vaargeulOverlays,\n      passeerstrookOverlays,\n      randstrookOverlays,\n      taludOverlays,\n    ].filter((c): c is GeoJSON.FeatureCollection => c != null);\n    if (collections.length === 0) return null;\n    return {\n      type: 'FeatureCollection',\n      features: collections.flatMap((c) => c.features),\n    };\n  }, [vaargeulOverlays, passeerstrookOverlays, randstrookOverlays, taludOverlays]);\n\n  const [referenceByIndex, setReferenceByIndex] = useState<\n    Array<{ ngd: number | null; ivd: number | null; ohd: number | null }>\n  >([]);\n\n  // Sample per-pixel reference depths uit de reference-COGs (#97).\n  // Bij 404 / geen COG valt `sampleReferenceLevelsAlongLine` terug op de scalar\n  // properties — zelfde gedrag als vóór #99.\n  useEffect(() => {\n    const line = state.crossSection.line;\n    if (!line || line.length < 2 || depthData.length === 0 || !baggervakken) {\n      setReferenceByIndex([]);\n      return;\n    }\n    let cancelled = false;\n    // Invalideer de oude mapping meteen zodat een opeenvolgende lijn met\n    // dezelfde sample-count niet voor één frame het oude reference-profiel\n    // toont terwijl de async sampling nog loopt.\n    setReferenceByIndex([]);\n\n    const [startLng, startLat] = line[0];\n    const [endLng, endLat] = line[1];\n    const totalDistance = haversineDistance(startLat, startLng, endLat, endLng);\n\n    // Één sample per depth-grid-point is voldoende: de reference-COG bevat de\n    // TIN-interpolerende oppervlakte van #97 op bathymetrie-resolutie, dus een\n    // passeerstrook/randstrook-rand zit al in het raster. De oude sub-step\n    // workaround (1 m langs de lijn om smalle randstroken niet te missen met\n    // polygon-lookup) is daardoor niet meer nodig.\n    const samples: ReferenceSampleInput[] = depthData.map((pt) => {\n      const t = totalDistance > 0 ? pt.distance / totalDistance : 0;\n      return {\n        lng: startLng + t * (endLng - startLng),\n        lat: startLat + t * (endLat - startLat),\n      };\n    });\n\n    (async () => {\n      const result = await sampleReferenceLevelsAlongLine(samples, baggervakken, depthOverlays);\n      if (!cancelled) setReferenceByIndex(result);\n    })();\n\n    return () => {\n      cancelled = true;\n    };\n  }, [depthData, state.crossSection.line, baggervakken, depthOverlays]);\n\n  const chartData = useMemo<ChartPoint[]>(() => {\n    if (referenceByIndex.length !== depthData.length) {\n      return depthData.map((pt) => ({ ...pt, ngd: null, ivd: null, ohd: null }));\n    }\n    return depthData.map((pt, i) => ({ ...pt, ...referenceByIndex[i] }));\n  }, [depthData, referenceByIndex]);\n\n  // Sample depth values from all visible COGs and merge results. ATP depths\n  // take priority over regular depths where both exist at the same sample.\n  useEffect(() => {\n    if (!state.crossSection.line || datasets.length === 0) return;\n    let cancelled = false;\n\n    (async () => {\n      setLoading(true);\n      try {\n        const atpEnabled = toontPeilsoort(state, 'atp');\n        const activeAtpDatasets = atpEnabled ? atpDatasets : [];\n\n        const [regularUrlsRaw, atpUrlsRaw] = await Promise.all([\n          Promise.all(datasets.map((ds) => getCogUrl(ds.id))),\n          Promise.all(activeAtpDatasets.map((ds) => getCogUrl(ds.id))),\n        ]);\n        const regularUrls = regularUrlsRaw.filter((u): u is string => u != null);\n        const atpUrls = atpUrlsRaw.filter((u): u is string => u != null);\n\n        if (regularUrls.length === 0 || cancelled) {\n          setDepthData([]);\n          return;\n        }\n\n        const [regularSeries, atpSeries] = await Promise.all([\n          Promise.all(\n            regularUrls.map((url) => sampleCrossSection(url, state.crossSection.line!, 5))\n          ),\n          Promise.all(atpUrls.map((url) => sampleCrossSection(url, state.crossSection.line!, 5))),\n        ]);\n        if (cancelled) return;\n\n        // Fold the regular series down the same way as before (first non-null wins).\n        const regularMerged =\n          regularSeries[0]?.map((pt, i) => ({\n            ...pt,\n            depth: regularSeries.reduce<number | null>(\n              (acc, series) => acc ?? series[i]?.depth ?? null,\n              null\n            ),\n          })) ?? [];\n\n        // Compute ATP-only depth per sample (first non-null across ATP series)\n        const atpOnlyDepth: (number | null)[] = regularMerged.map((_, i) => {\n          for (const series of atpSeries) {\n            const d = series[i]?.depth;\n            if (d != null) return d;\n          }\n          return null;\n        });\n\n        // Merged depth (ATP wins over regular) for numeric consumers.\n        const merged = mergeAtpPriority(regularMerged, atpSeries).map((pt, i) => ({\n          ...pt,\n          atpDepth: atpOnlyDepth[i],\n        }));\n\n        setDepthData(merged);\n      } catch {\n        if (!cancelled) setDepthData([]);\n      } finally {\n        if (!cancelled) setLoading(false);\n      }\n    })();\n\n    return () => {\n      cancelled = true;\n    };\n  }, [state.crossSection.line, datasets, atpDatasets, state.peilsoorten]);\n\n  // Y-axis domain: always include NAP=0, with zoom support\n  const [yZoom, setYZoom] = useState<[number, number] | null>(null);\n\n  const autoYDomain = useMemo<[number, number]>(() => {\n    const depths = chartData.filter((p) => p.depth != null).map((p) => p.depth!);\n    if (depths.length === 0) return [-20, 2];\n    const min = Math.min(...depths);\n    return [min - 1, PANAMAX_FREEBOARD + 2];\n  }, [chartData]);\n\n  const yDomain = yZoom ?? autoYDomain;\n\n  const onChartWheel = useCallback(\n    (e: React.WheelEvent) => {\n      e.preventDefault();\n      const [lo, hi] = yZoom ?? autoYDomain;\n      const range = hi - lo;\n      const mid = (lo + hi) / 2;\n      const factor = e.deltaY > 0 ? 1.15 : 0.87; // scroll down = zoom out\n      const newRange = Math.max(3, range * factor); // min 3m range\n      setYZoom([mid - newRange / 2, mid + newRange / 2]);\n    },\n    [yZoom, autoYDomain]\n  );\n\n  const resetZoom = useCallback(() => setYZoom(null), []);\n\n  // Reset zoom when data changes\n  useEffect(() => setYZoom(null), [depthData]);\n\n  if (!state.crossSection.line) return null;\n\n  const filteredData = chartData.filter((p) => p.depth != null);\n\n  return (\n    <section\n      ref={ladeRef}\n      aria-label=\"Dwarsprofiel\"\n      className=\"relative shrink-0 border-t bg-background\"\n      style={{ height }}\n    >\n      {/* Sleeprand: de hele bovenrand, met een greepje in het midden. */}\n      <div\n        role=\"separator\"\n        aria-orientation=\"horizontal\"\n        aria-label=\"Hoogte dwarsprofiel\"\n        aria-valuenow={height}\n        aria-valuemin={MIN_HEIGHT}\n        aria-valuemax={maxHoogte ?? undefined}\n        tabIndex={0}\n        // Een zichtbare focusring: het greepje alleen is te klein om te zien\n        // dat de rand de focus heeft.\n        className=\"absolute inset-x-0 -top-1.5 z-10 flex h-3 cursor-ns-resize touch-none items-center justify-center rounded-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden [&:focus-visible>span]:h-1.5 [&:focus-visible>span]:w-14 [&:focus-visible>span]:bg-primary\"\n        onPointerDown={onSleepStart}\n        onPointerMove={onSleep}\n        onPointerUp={onSleepEinde}\n        onPointerCancel={onSleepEinde}\n        onKeyDown={(e) => {\n          if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n          e.preventDefault();\n          // Ook de tijdlijn luistert naar pijltjes, maar alleen links en rechts.\n          setHeight((h) => begrens(h + (e.key === 'ArrowUp' ? TOETS_STAP : -TOETS_STAP)));\n        }}\n      >\n        <span className=\"h-1 w-10 rounded-full bg-border\" />\n      </div>\n\n      <div className=\"flex h-10 items-center justify-between border-b px-3\">\n        <div className=\"flex items-center gap-2\">\n          <Scissors className=\"size-4 text-muted-foreground\" />\n          <h2 className=\"text-sm font-medium\">Dwarsprofiel</h2>\n          {loading && <span className=\"text-xs text-muted-foreground\">Laden…</span>}\n        </div>\n        <div className=\"flex items-center gap-0.5\">\n          {yZoom && (\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-8\"\n              title=\"Zoom herstellen\"\n              aria-label=\"Zoom herstellen\"\n              onClick={resetZoom}\n            >\n              <RotateCcw className=\"size-4\" />\n            </Button>\n          )}\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-8\"\n            aria-label=\"Dwarsprofiel sluiten\"\n            title=\"Dwarsprofiel sluiten\"\n            onClick={() => dispatch({ type: 'CANCEL_CROSS_SECTION' })}\n          >\n            <X className=\"size-4\" />\n          </Button>\n        </div>\n      </div>\n\n      {/* Chart */}\n      <div className=\"h-[calc(100%-40px)] px-4 py-2\" onWheel={onChartWheel}>\n        {filteredData.length > 0 ? (\n          <ChartContainer config={chartConfig} className=\"h-full w-full\">\n            <ComposedChart data={filteredData} margin={{ top: 10, right: 20, bottom: 0, left: 10 }}>\n              <defs>\n                {/* Water gradient: lighter at surface, darker at depth */}\n                <linearGradient id=\"waterGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                  <stop offset=\"0%\" stopColor=\"#60a5fa\" stopOpacity={0.25} />\n                  <stop offset=\"100%\" stopColor=\"#1e40af\" stopOpacity={0.45} />\n                </linearGradient>\n                {/* Sand gradient for seabed fill */}\n                <linearGradient id=\"sandGradient\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                  <stop offset=\"0%\" stopColor=\"#C4A46C\" stopOpacity={0.85} />\n                  <stop offset=\"100%\" stopColor=\"#8B7355\" stopOpacity={0.95} />\n                </linearGradient>\n              </defs>\n              <CartesianGrid strokeDasharray=\"3 3\" />\n              {/* Water zone: blue fill from chart bottom up to NAP=0 */}\n              <ReferenceArea\n                y1={yDomain[0]}\n                y2={0}\n                fill=\"url(#waterGradient)\"\n                fillOpacity={1}\n                ifOverflow=\"hidden\"\n              />\n              <XAxis\n                dataKey=\"distance\"\n                type=\"number\"\n                tickFormatter={(v: number) => `${getal(v)} m`}\n                fontSize={12}\n              />\n              <YAxis\n                domain={yDomain}\n                tickFormatter={(v: number) => getal(v, 1)}\n                fontSize={12}\n                label={{\n                  value: 'm NAP',\n                  angle: -90,\n                  position: 'insideLeft',\n                  style: { fontSize: 12 },\n                }}\n              />\n              <ChartTooltip\n                content={\n                  <ChartTooltipContent\n                    formatter={(value, name) => {\n                      const label = chartConfig[name as keyof typeof chartConfig]?.label ?? name;\n                      if (value == null) return `${label}: —`;\n                      return `${label}: ${getal(value as number, 2)} m NAP`;\n                    }}\n                  />\n                }\n              />\n              {/* Ground: sandy fill from seabed down to chart bottom */}\n              <Area\n                type=\"monotone\"\n                dataKey=\"depth\"\n                stroke=\"#6B5B3E\"\n                fill=\"url(#sandGradient)\"\n                fillOpacity={1}\n                strokeWidth={2}\n                dot={false}\n                isAnimationActive={false}\n                baseValue={yDomain[0]}\n              />\n              {/* Water surface at NAP=0 */}\n              <ReferenceLine\n                y={0}\n                stroke=\"#3b82f6\"\n                strokeWidth={1.5}\n                strokeDasharray=\"6 3\"\n                label={{ value: 'NAP', position: 'right', fill: '#3b82f6', fontSize: 12 }}\n                ifOverflow=\"hidden\"\n              />\n              <Line\n                type=\"monotone\"\n                dataKey=\"atpDepth\"\n                stroke=\"#d946ef\"\n                strokeWidth={2.5}\n                strokeDasharray=\"4 3\"\n                dot={false}\n                connectNulls={false}\n                name=\"Aantoonpeiling\"\n                isAnimationActive={false}\n              />\n              <Line\n                type=\"monotone\"\n                dataKey=\"ngd\"\n                stroke={REF_LEVEL_COLORS.ngd}\n                strokeDasharray=\"5 5\"\n                strokeWidth={1.5}\n                dot={false}\n                connectNulls={false}\n                name=\"NGD\"\n                isAnimationActive={false}\n              />\n              <Line\n                type=\"monotone\"\n                dataKey=\"ohd\"\n                stroke={REF_LEVEL_COLORS.ohd}\n                strokeDasharray=\"5 5\"\n                strokeWidth={1.5}\n                dot={false}\n                connectNulls={false}\n                name=\"max.\"\n                isAnimationActive={false}\n              />\n              {/* Panamax ship hull cross-section (to scale) */}\n              <Customized component={PanamaxHullRechartsAdapter} />\n            </ComposedChart>\n          </ChartContainer>\n        ) : (\n          <div className=\"flex h-full items-center justify-center\">\n            <p className=\"text-xs text-muted-foreground\">\n              {loading\n                ? 'Diepteprofiel wordt berekend...'\n                : 'Geen dieptedata beschikbaar voor deze lijn.'}\n            </p>\n          </div>\n        )}\n      </div>\n    </section>\n  );\n}\n","import type { CrossSectionPoint } from './cross-section';\n\n/**\n * Merge the regular peiling cross-section with one or more ATP cross-sections.\n * For every sample point the first non-null ATP depth wins, otherwise the\n * base depth is preserved.\n *\n * All series must have identical length + matching `distance` values; this\n * invariant is guaranteed upstream because all samples are taken from the\n * same line with the same sample interval.\n */\nexport function mergeAtpPriority(\n  base: CrossSectionPoint[],\n  atpSeries: CrossSectionPoint[][],\n): CrossSectionPoint[] {\n  if (atpSeries.length === 0) return base;\n  return base.map((pt, i) => {\n    for (const series of atpSeries) {\n      const atpDepth = series[i]?.depth;\n      if (atpDepth != null) {\n        return { ...pt, depth: atpDepth };\n      }\n    }\n    return pt;\n  });\n}\n","import { Box, Hexagon, Minus, Plus, Scissors } from 'lucide-react';\n\nimport { useKaart } from './KaartContext';\n\nimport type { MapRef } from 'react-map-gl/maplibre';\n\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '@/js/components/ui/tooltip';\n\ninterface MapToolbarProps {\n  mapRef: React.RefObject<MapRef | null>;\n  /** Gebied meten begint: het werkblad gaat open op Gebied, waar de uitkomst komt. */\n  onGebiedStart?: () => void;\n}\n\nexport default function MapToolbar({ mapRef, onGebiedStart }: MapToolbarProps) {\n  const { state, dispatch } = useKaart();\n\n  const handleToggle3D = () => {\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    dispatch({ type: 'TOGGLE_3D' });\n\n    if (!state.is3D) {\n      // Switch to 3D\n      map.easeTo({ pitch: 60, duration: 800 });\n    } else {\n      // Switch to 2D\n      map.easeTo({ pitch: 0, bearing: 0, duration: 800 });\n    }\n  };\n\n  const handleCrossSection = () => {\n    if (state.crossSection.drawing) {\n      dispatch({ type: 'CANCEL_CROSS_SECTION' });\n    } else {\n      dispatch({ type: 'START_CROSS_SECTION' });\n    }\n  };\n\n  const handleGebied = () => {\n    if (state.gebied.drawing) {\n      dispatch({ type: 'CANCEL_GEBIED' });\n    } else {\n      dispatch({ type: 'START_GEBIED' });\n      onGebiedStart?.();\n    }\n  };\n\n  const handleZoomIn = () => mapRef.current?.getMap()?.zoomIn();\n  const handleZoomOut = () => mapRef.current?.getMap()?.zoomOut();\n\n  return (\n    <TooltipProvider delayDuration={300}>\n      {/* Gereedschap eerst, zoom eronder: de knoppen die iets met de kaart\n          doen staan bovenaan, waar de hand als eerste komt. */}\n      <div\n        role=\"toolbar\"\n        aria-label=\"Meetgereedschap\"\n        aria-orientation=\"vertical\"\n        className=\"absolute top-4 right-4 z-20 flex flex-col gap-0.5 rounded-xl bg-background p-1 shadow-md\"\n      >\n        <ToolbarButton\n          label=\"Dwarsprofiel\"\n          tooltip={state.crossSection.drawing ? 'Dwarsprofiel annuleren' : 'Dwarsprofiel tekenen'}\n          onClick={handleCrossSection}\n          icon={<Scissors className=\"size-[18px]\" />}\n          active={state.crossSection.drawing}\n        />\n        <ToolbarButton\n          label=\"Gebied meten\"\n          tooltip={\n            state.gebied.drawing\n              ? 'Gebied tekenen annuleren (Esc)'\n              : 'Gebied meten: klik hoekpunten, dubbelklik om te sluiten'\n          }\n          onClick={handleGebied}\n          icon={<Hexagon className=\"size-[18px]\" />}\n          active={state.gebied.drawing}\n        />\n        <ToolbarButton\n          label=\"3D\"\n          tooltip={state.is3D ? '2D weergave' : '3D weergave'}\n          onClick={handleToggle3D}\n          icon={<Box className=\"size-[18px]\" />}\n          active={state.is3D}\n        />\n        <div className=\"mx-1 my-0.5 border-t\" />\n        <ToolbarButton\n          tooltip=\"Inzoomen\"\n          onClick={handleZoomIn}\n          icon={<Plus className=\"size-[18px]\" />}\n        />\n        <ToolbarButton\n          tooltip=\"Uitzoomen\"\n          onClick={handleZoomOut}\n          icon={<Minus className=\"size-[18px]\" />}\n        />\n      </div>\n    </TooltipProvider>\n  );\n}\n\nfunction ToolbarButton({\n  label,\n  tooltip,\n  onClick,\n  icon,\n  active,\n}: {\n  /**\n   * Vaste naam voor een schakelknop. De stand zit in aria-pressed; een label\n   * dat meeverandert (\"… annuleren, ingedrukt\") zegt hetzelfde twee keer, en\n   * tegenstrijdig. Zonder label is de tooltip de naam.\n   */\n  label?: string;\n  tooltip: string;\n  onClick: () => void;\n  icon: React.ReactNode;\n  active?: boolean;\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          variant={active ? 'default' : 'ghost'}\n          size=\"icon\"\n          className=\"size-10 rounded-lg\"\n          aria-label={label ?? tooltip}\n          aria-pressed={active}\n          onClick={onClick}\n        >\n          {icon}\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent side=\"left\" className=\"text-xs\">\n        {tooltip}\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { Layer, Source } from 'react-map-gl/maplibre';\n\nimport { useKaart } from './KaartContext';\n\nimport type { MapLayerMouseEvent, MapRef } from 'react-map-gl/maplibre';\n\ninterface CrossSectionToolProps {\n  mapRef: React.RefObject<MapRef | null>;\n}\n\n/**\n * Renders the cross-section drawing interaction on the map.\n * When drawing mode is active, user clicks define start and end points.\n */\nexport default function CrossSectionTool({ mapRef }: CrossSectionToolProps) {\n  const { state, dispatch } = useKaart();\n  const [startPoint, setStartPoint] = useState<[number, number] | null>(null);\n\n  // Reset start point when drawing starts\n  useEffect(() => {\n    if (state.crossSection.drawing) {\n      setStartPoint(null);\n    }\n  }, [state.crossSection.drawing]);\n\n  // Set crosshair cursor during drawing\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    if (state.crossSection.drawing) {\n      map.getCanvas().style.cursor = 'crosshair';\n      return () => {\n        map.getCanvas().style.cursor = '';\n      };\n    }\n  }, [state.crossSection.drawing, mapRef]);\n\n  const handleClick = useCallback(\n    (e: MapLayerMouseEvent) => {\n      if (!state.crossSection.drawing) return;\n\n      const point: [number, number] = [e.lngLat.lng, e.lngLat.lat];\n\n      if (!startPoint) {\n        setStartPoint(point);\n      } else {\n        dispatch({\n          type: 'SET_CROSS_SECTION_LINE',\n          line: [startPoint, point],\n        });\n        setStartPoint(null);\n      }\n    },\n    [state.crossSection.drawing, startPoint, dispatch]\n  );\n\n  // Attach click handler to map\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !state.crossSection.drawing) return;\n\n    const handler = (e: maplibregl.MapMouseEvent) => {\n      handleClick(e as unknown as MapLayerMouseEvent);\n    };\n\n    map.on('click', handler);\n    return () => {\n      map.off('click', handler);\n    };\n  }, [mapRef, state.crossSection.drawing, handleClick]);\n\n  // Keep cross-section layers on top after COG layer swaps\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    const moveToTop = () => {\n      if (map.getLayer('cross-section-line')) map.moveLayer('cross-section-line');\n      if (map.getLayer('cross-section-points')) map.moveLayer('cross-section-points');\n    };\n\n    map.on('idle', moveToTop);\n    moveToTop();\n\n    return () => {\n      map.off('idle', moveToTop);\n    };\n  }, [mapRef, state.crossSection.drawing, state.crossSection.line]);\n\n  // Build GeoJSON for the drawn line and points\n  const lineGeoJson: GeoJSON.FeatureCollection = {\n    type: 'FeatureCollection',\n    features: [\n      // Completed line\n      ...(state.crossSection.line\n        ? [\n            {\n              type: 'Feature' as const,\n              geometry: {\n                type: 'LineString' as const,\n                coordinates: state.crossSection.line,\n              },\n              properties: {},\n            },\n            ...state.crossSection.line.map((coord) => ({\n              type: 'Feature' as const,\n              geometry: { type: 'Point' as const, coordinates: coord },\n              properties: {},\n            })),\n          ]\n        : []),\n      // Start point during drawing\n      ...(startPoint\n        ? [\n            {\n              type: 'Feature' as const,\n              geometry: {\n                type: 'Point' as const,\n                coordinates: startPoint,\n              },\n              properties: {},\n            },\n          ]\n        : []),\n    ],\n  };\n\n  if (!state.crossSection.drawing && !state.crossSection.line) return null;\n\n  return (\n    <Source id=\"cross-section\" type=\"geojson\" data={lineGeoJson}>\n      <Layer\n        id=\"cross-section-line\"\n        type=\"line\"\n        filter={['==', '$type', 'LineString']}\n        paint={{\n          'line-color': '#ef4444',\n          'line-width': 3,\n          'line-dasharray': [2, 2],\n        }}\n      />\n      <Layer\n        id=\"cross-section-points\"\n        type=\"circle\"\n        filter={['==', '$type', 'Point']}\n        paint={{\n          'circle-radius': 6,\n          'circle-color': '#ef4444',\n          'circle-stroke-color': '#ffffff',\n          'circle-stroke-width': 2,\n        }}\n      />\n    </Source>\n  );\n}\n","import { X } from 'lucide-react';\n\nimport type { DepthReading } from '@/js/lib/depth-at-point';\nimport type { KaartReferentie } from '@/js/lib/depth-colors';\nimport type { SpeldAnker } from '@/js/lib/speld-anker';\n\nimport { getal } from '@/js/lib/getal-notatie';\nimport { herkomstTekst } from '@/js/lib/omgeving';\nimport { REF_LEVEL_LABELS } from '@/js/lib/reference-levels';\nimport { cn } from '@/js/lib/utils';\n\ninterface Props {\n  /** Null zolang de diepte op dit punt nog gelezen wordt. */\n  reading: DepthReading | null;\n  referenceLevel: KaartReferentie;\n  onSluit: () => void;\n  /** Kant van het punt waar het label staat; zie speldAnker. */\n  anker?: SpeldAnker;\n}\n\nfunction meters(value: number | null, opties?: { plusteken?: boolean }): string {\n  if (value == null) return '—';\n  return `${getal(value, 2, opties)} m`;\n}\n\n/**\n * Het label van de speld: de diepte op het punt waar geklikt is (ontwerp\n * 2026-10-15, §5). Vervangt de doorlopende uitlezing onder de muis, die bij\n * elke beweging een pixel uit blob-storage las en meebewoog met alles wat je\n * op de kaart deed.\n *\n * Buiten NAP staat de kaart in verschilkleuren; dan noemt het label ook het\n * vlak en het verschil, anders zou het iets anders zeggen dan wat je ziet.\n */\nexport default function DiepteSpeld({ reading, referenceLevel, onSluit, anker = 'bottom' }: Props) {\n  const eronder = anker.startsWith('top');\n  return (\n    // data-speld: een klik hierop is geen nieuwe klik op de kaart, zie MapView.\n    // Staat het label onder het punt, dan draait de volgorde om: stip, steel, label.\n    <div\n      data-speld\n      data-anker={anker}\n      className={cn(\n        'flex',\n        eronder ? 'flex-col-reverse' : 'flex-col',\n        anker.endsWith('-left')\n          ? 'items-start'\n          : anker.endsWith('-right')\n            ? 'items-end'\n            : 'items-center'\n      )}\n    >\n      <div\n        role=\"status\"\n        aria-label=\"Diepte op dit punt\"\n        className=\"min-w-44 rounded-xl border bg-background py-2 pr-1.5 pl-3 text-xs shadow-md\"\n      >\n        <div className=\"flex items-center justify-between gap-3\">\n          <span className=\"font-medium text-muted-foreground\">\n            {reading ? (reading.plek ?? 'buiten de vakken') : 'Diepte lezen…'}\n          </span>\n          <button\n            type=\"button\"\n            aria-label=\"Speld weghalen (Esc)\"\n            title=\"Speld weghalen (Esc)\"\n            onClick={onSluit}\n            className=\"flex size-6 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground\"\n          >\n            <X className=\"size-3.5\" />\n          </button>\n        </div>\n        {reading && <SpeldInhoud reading={reading} referenceLevel={referenceLevel} />}\n      </div>\n      {/* Steel en stip samen zo breed als de stip: aan de zijkant uitgelijnd\n          staat de steel dan nog boven het midden van de stip. */}\n      <div\n        aria-hidden\n        className={cn('flex w-3 items-center', eronder ? 'flex-col-reverse' : 'flex-col')}\n      >\n        <span className=\"h-2 w-px bg-foreground/60\" />\n        <span className=\"size-3 rounded-full border-2 border-background bg-primary shadow-[0_0_0_1px_rgba(0,0,0,.2)]\" />\n      </div>\n    </div>\n  );\n}\n\nfunction SpeldInhoud({\n  reading,\n  referenceLevel,\n}: {\n  reading: DepthReading;\n  referenceLevel: KaartReferentie;\n}) {\n  const { vakCode, depthNap, referenceNap, deltaM, onzekerheid, bron, verwacht } = reading;\n  return (\n    <div className=\"pr-1.5\">\n      <div className=\"getal text-[15px] font-medium text-foreground\">\n        {depthNap != null ? `${meters(depthNap)} NAP` : 'geen meting'}\n      </div>\n      {onzekerheid != null && (\n        <div className=\"mt-0.5 text-muted-foreground\" data-testid=\"omgeving-zekerheid\">\n          {herkomstTekst(bron, verwacht)}\n        </div>\n      )}\n      {/* Het vlak hoort bij een vak; buiten de vakken is er niets om tegen af\n          te zetten. */}\n      {referenceLevel !== 'nap' && vakCode && depthNap != null && (\n        <div className=\"mt-1 flex flex-col gap-0.5 border-t pt-1 text-muted-foreground\">\n          <div className=\"flex justify-between gap-3\">\n            <span>\n              {referenceLevel === 'verschil' ? 'vorige peiling' : REF_LEVEL_LABELS[referenceLevel]}\n            </span>\n            <span className=\"getal\">{meters(referenceNap)}</span>\n          </div>\n          <div className=\"flex justify-between gap-3\">\n            {/* LAT is een peil: de bodem ligt eronder en dat heet diepte, geen\n                verschil. Bij een ontwerpdiepte gaat het juist om de kant waar\n                de bodem op zit, dus daar hoort het teken erbij (#114). Bij de\n                verschilkaart is plus opgehoogd en min verdiept; geen van\n                beide is op zich fout, dus geen rood. */}\n            <span>\n              {referenceLevel === 'lat'\n                ? 'diepte t.o.v. LAT'\n                : referenceLevel === 'verschil'\n                  ? 'verschil met vorige peiling'\n                  : `verschil met ${REF_LEVEL_LABELS[referenceLevel]}`}\n            </span>\n            <span\n              className={\n                referenceLevel !== 'lat' &&\n                referenceLevel !== 'verschil' &&\n                deltaM != null &&\n                deltaM > 0\n                  ? 'getal font-medium text-destructive'\n                  : 'getal'\n              }\n            >\n              {meters(deltaM, { plusteken: referenceLevel !== 'lat' })}\n            </span>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n","import { getal } from './getal-notatie';\nimport { rdToWgs84 } from './projections';\n\n/**\n * Helpers voor het zelfgetekende meetgebied (#115, WP3 taak 2).\n *\n * Het tekenen gebeurt in de browser, het rekenen op de server: een gebied van\n * 500 bij 500 meter is op 1 meter resolutie al 250.000 pixels, en die stuk\n * voor stuk uit de COG lezen zoals het dwarsprofiel dat voor een lijn doet is\n * niet werkbaar. Wat hier staat gaat dus alleen over de vorm en over hoe de\n * uitkomst gelezen wordt, niet over de statistiek zelf.\n */\n\n/** Losse hoekpunten in WGS84, zoals MapLibre ze aanlevert. */\nexport type Ring = [number, number][];\n\n/** Minimaal aantal hoekpunten voor een vlak. */\nexport const MIN_HOEKPUNTEN = 3;\n\n/**\n * De tekening als GeoJSON: vlak, omtrek en hoekpunten in een collectie.\n *\n * Tijdens het tekenen hangt er een elastieklijn aan de cursor, zodat zichtbaar\n * is welk vlak de volgende klik oplevert. Het vlak verschijnt vanaf drie\n * punten; daarvoor is er alleen een lijn.\n */\nexport function gebiedGeoJSON(ring: Ring, cursor: [number, number] | null): GeoJSON.FeatureCollection {\n  const punten = cursor ? [...ring, cursor] : ring;\n  const features: GeoJSON.Feature[] = [];\n\n  if (punten.length >= MIN_HOEKPUNTEN) {\n    features.push({\n      type: 'Feature',\n      properties: {},\n      geometry: { type: 'Polygon', coordinates: [[...punten, punten[0]]] },\n    });\n  } else if (punten.length >= 2) {\n    features.push({\n      type: 'Feature',\n      properties: {},\n      geometry: { type: 'LineString', coordinates: punten },\n    });\n  }\n\n  // Alleen de vastgeklikte hoekpunten krijgen een stip; de cursorpositie niet,\n  // die is nog geen punt.\n  for (const [index, punt] of ring.entries()) {\n    features.push({\n      type: 'Feature',\n      properties: { eerste: index === 0 },\n      geometry: { type: 'Point', coordinates: punt },\n    });\n  }\n\n  return { type: 'FeatureCollection', features };\n}\n\n/**\n * Het ondiepste punt van het gebied als GeoJSON-marker.\n *\n * `shallowest_point` komt in EPSG:28992 uit de backend, uit dezelfde pixel als\n * de gerapporteerde waarde. MapLibre wil WGS84.\n */\nexport function gebiedMarkerGeoJSON(\n  punt: number[] | null | undefined,\n  label: string\n): GeoJSON.FeatureCollection {\n  if (!punt || punt.length < 2) return { type: 'FeatureCollection', features: [] };\n  const [lng, lat] = rdToWgs84(punt[0], punt[1]);\n  return {\n    type: 'FeatureCollection',\n    features: [\n      {\n        type: 'Feature',\n        properties: { label },\n        geometry: { type: 'Point', coordinates: [lng, lat] },\n      },\n    ],\n  };\n}\n\n// Alle getallen via getal(): met een echt minteken, zoals in de rest van het\n// werkblad, zodat een kolom diepten in Plex Mono op de komma uitlijnt.\n\n/** Oppervlak in de eenheid waarin een baggeraar het uitspreekt. */\nexport function formatOppervlak(m2: number | null | undefined): string {\n  if (m2 == null) return '—';\n  if (m2 >= 10_000) return `${getal(m2 / 10_000, 2)} ha`;\n  return `${getal(m2)} m²`;\n}\n\n/** Diepte in m NAP, twee decimalen, of een streepje als er niets is. */\nexport function formatDiepte(m: number | null | undefined): string {\n  if (m == null) return '—';\n  return `${getal(m, 2)} m`;\n}\n\n/** Volume of oppervlak uit een kubering, afgerond op hele eenheden. */\nexport function formatKubering(waarde: number | null | undefined, eenheid: string): string {\n  if (waarde == null) return '—';\n  return `${getal(waarde)} ${eenheid}`;\n}\n\n/**\n * Afstand tot de NGD in centimeters, met teken.\n *\n * Positief betekent boven de gegarandeerde diepte, dus in de richting waar\n * gebaggerd moet worden. Dat teken is de reden dat dit niet als kaal getal\n * getoond wordt.\n */\nexport function formatCmBovenNgd(cm: number | null | undefined): string {\n  if (cm == null) return '—';\n  return `${getal(cm, 0, { plusteken: true })} cm`;\n}\n\n/**\n * Hoeveel van het getekende gebied daadwerkelijk gemeten is, als fractie.\n *\n * Loopt dit ver onder de 1, dan steekt het gebied buiten de peiling of buiten\n * een baggervak. De cijfers kloppen dan nog steeds, maar ze gaan over minder\n * dan wat er op het scherm getekend staat, en dat moet de gebruiker weten.\n */\nexport function dekking(areaM2: number, polygonAreaM2: number): number | null {\n  if (!polygonAreaM2) return null;\n  return areaM2 / polygonAreaM2;\n}\n\n/** Onder deze dekking is de waarschuwing de moeite van het tonen waard. */\nexport const DEKKING_DREMPEL = 0.95;\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Layer, Source } from 'react-map-gl/maplibre';\n\nimport { useKaart } from './KaartContext';\n\nimport type { MapRef } from 'react-map-gl/maplibre';\n\nimport {\n  formatDiepte,\n  gebiedGeoJSON,\n  gebiedMarkerGeoJSON,\n  MIN_HOEKPUNTEN,\n  type Ring,\n} from '@/js/lib/gebied';\n\n/**\n * Het tekenen van een meetgebied op de kaart (#115, WP3 taak 2).\n *\n * Klikken zet een hoekpunt, een elastieklijn volgt de cursor. Sluiten kan op\n * drie manieren, omdat verschillende mensen verschillende gewoontes hebben:\n * klikken op het eerste punt, dubbelklikken, of Enter. Backspace haalt het\n * laatste punt weg en Escape breekt af.\n *\n * De uitkomst van de berekening komt hier ook langs: het ondiepste punt van\n * het gebied wordt als marker getekend, zodat het getal in het paneel en de\n * plek op de kaart bij elkaar horen.\n */\n\nconst KLIK_STRAAL_PX = 12;\nconst GEBIED_KLEUR = '#0ea5e9';\n\ninterface GebiedToolProps {\n  mapRef: React.RefObject<MapRef | null>;\n}\n\nexport default function GebiedTool({ mapRef }: GebiedToolProps) {\n  const { state, dispatch } = useKaart();\n  const { drawing, ring: vastgelegdeRing, resultaat } = state.gebied;\n\n  // De hoekpunten staan zowel in state (om te tekenen) als in een ref (om in\n  // de kaart-handlers te lezen). Die handlers hangen aan MapLibre en niet aan\n  // React, dus zonder ref zou een dubbelklik de ring van twee klikken geleden\n  // zien: precies de situatie waarin het vlak wordt afgesloten.\n  const ringRef = useRef<Ring>([]);\n  const [ring, setRingState] = useState<Ring>([]);\n  const [cursor, setCursor] = useState<[number, number] | null>(null);\n\n  const zetRing = useCallback((punten: Ring) => {\n    ringRef.current = punten;\n    setRingState(punten);\n  }, []);\n\n  // Elke nieuwe tekensessie begint leeg.\n  useEffect(() => {\n    if (drawing) {\n      zetRing([]);\n      setCursor(null);\n    }\n  }, [drawing, zetRing]);\n\n  const afronden = useCallback(() => {\n    const punten = ringRef.current;\n    if (punten.length < MIN_HOEKPUNTEN) return;\n    setCursor(null);\n    dispatch({ type: 'SET_GEBIED_RING', ring: punten });\n  }, [dispatch]);\n\n  // Klik, dubbelklik en muisbeweging op de kaart.\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !drawing) return;\n\n    const dichtbij = (a: [number, number], b: [number, number]) => {\n      const pa = map.project(a);\n      const pb = map.project(b);\n      return Math.hypot(pa.x - pb.x, pa.y - pb.y) <= KLIK_STRAAL_PX;\n    };\n\n    const opKlik = (e: maplibregl.MapMouseEvent) => {\n      const punt: [number, number] = [e.lngLat.lng, e.lngLat.lat];\n      const huidige = ringRef.current;\n\n      // Klikken op het eerste hoekpunt sluit het vlak. De test staat in\n      // schermpixels en niet in graden, want de tolerantie moet bij elk\n      // zoomniveau even groot aanvoelen.\n      if (huidige.length >= MIN_HOEKPUNTEN && dichtbij(huidige[0], punt)) {\n        afronden();\n        return;\n      }\n\n      zetRing([...huidige, punt]);\n    };\n\n    const opDubbelklik = (e: maplibregl.MapMouseEvent) => {\n      // Zonder dit zoomt de kaart in terwijl het vlak gesloten wordt.\n      e.preventDefault();\n      // De twee klikken van de dubbelklik hebben er al hoekpunten bij gezet.\n      // Die liggen bovenop elkaar en zouden een naald aan de rand opleveren,\n      // dus ze gaan er weer af.\n      const punten = [...ringRef.current];\n      while (punten.length > MIN_HOEKPUNTEN && dichtbij(punten[punten.length - 1], punten[punten.length - 2])) {\n        punten.pop();\n      }\n      zetRing(punten);\n      // `afronden` leest de ref, die hierboven is bijgewerkt.\n      afronden();\n    };\n\n    const opBeweging = (e: maplibregl.MapMouseEvent) => {\n      setCursor([e.lngLat.lng, e.lngLat.lat]);\n    };\n\n    map.on('click', opKlik);\n    map.on('dblclick', opDubbelklik);\n    map.on('mousemove', opBeweging);\n    return () => {\n      map.off('click', opKlik);\n      map.off('dblclick', opDubbelklik);\n      map.off('mousemove', opBeweging);\n    };\n  }, [mapRef, drawing, afronden, zetRing]);\n\n  // Toetsenbord: afronden, laatste punt terug, of afbreken.\n  useEffect(() => {\n    if (!drawing) return;\n\n    const opToets = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        dispatch({ type: 'CANCEL_GEBIED' });\n      } else if (e.key === 'Enter') {\n        afronden();\n      } else if (e.key === 'Backspace') {\n        e.preventDefault();\n        zetRing(ringRef.current.slice(0, -1));\n      }\n    };\n\n    window.addEventListener('keydown', opToets);\n    return () => window.removeEventListener('keydown', opToets);\n  }, [drawing, afronden, zetRing, dispatch]);\n\n  // Kruisdraad zolang er getekend wordt.\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !drawing) return;\n    map.getCanvas().style.cursor = 'crosshair';\n    return () => {\n      map.getCanvas().style.cursor = '';\n    };\n  }, [drawing, mapRef]);\n\n  // De COG-lagen worden bij elke datumwissel opnieuw toegevoegd en komen dan\n  // bovenop te liggen. Net als bij het dwarsprofiel duwen we onze lagen daarna\n  // terug naar boven.\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    const naarBoven = () => {\n      for (const id of [\n        'gebied-vlak',\n        'gebied-omtrek',\n        'gebied-hoekpunten',\n        'gebied-ondiepste-halo',\n        'gebied-ondiepste-stip',\n        'gebied-ondiepste-label',\n      ]) {\n        if (map.getLayer(id)) map.moveLayer(id);\n      }\n    };\n\n    map.on('idle', naarBoven);\n    naarBoven();\n    return () => {\n      map.off('idle', naarBoven);\n    };\n  }, [mapRef, drawing, vastgelegdeRing, resultaat]);\n\n  const geojson = useMemo(\n    () => gebiedGeoJSON(drawing ? ring : (vastgelegdeRing ?? []), drawing ? cursor : null),\n    [drawing, ring, vastgelegdeRing, cursor]\n  );\n\n  const markerGeoJson = useMemo(\n    () =>\n      gebiedMarkerGeoJSON(\n        resultaat?.shallowest_point,\n        // Op de kaart een koppelteken, net als de dieptecijfers: dit label\n        // komt uit het lettertype van de ondergrond, en dat kent het\n        // minteken niet zeker.\n        `Ondiepst  ${formatDiepte(resultaat?.depth_max).replace('−', '-')}`\n      ),\n    [resultaat]\n  );\n\n  if (!drawing && !vastgelegdeRing) return null;\n\n  return (\n    <>\n      <Source id=\"gebied\" type=\"geojson\" data={geojson}>\n        <Layer\n          id=\"gebied-vlak\"\n          type=\"fill\"\n          filter={['==', '$type', 'Polygon']}\n          paint={{ 'fill-color': GEBIED_KLEUR, 'fill-opacity': 0.15 }}\n        />\n        <Layer\n          id=\"gebied-omtrek\"\n          type=\"line\"\n          filter={['!=', '$type', 'Point']}\n          paint={{\n            'line-color': GEBIED_KLEUR,\n            'line-width': 2.5,\n            'line-dasharray': drawing ? [2, 2] : [1, 0],\n          }}\n        />\n        <Layer\n          id=\"gebied-hoekpunten\"\n          type=\"circle\"\n          filter={['==', '$type', 'Point']}\n          paint={{\n            // Het eerste punt is dikker: daar klik je op om te sluiten.\n            'circle-radius': ['case', ['get', 'eerste'], 6, 4],\n            'circle-color': '#ffffff',\n            'circle-stroke-color': GEBIED_KLEUR,\n            'circle-stroke-width': 2,\n          }}\n        />\n      </Source>\n\n      {markerGeoJson.features.length > 0 && (\n        <Source id=\"gebied-ondiepste\" type=\"geojson\" data={markerGeoJson}>\n          <Layer\n            id=\"gebied-ondiepste-halo\"\n            type=\"circle\"\n            paint={{ 'circle-radius': 10, 'circle-color': '#e11d48', 'circle-opacity': 0.25 }}\n          />\n          <Layer\n            id=\"gebied-ondiepste-stip\"\n            type=\"circle\"\n            paint={{\n              'circle-radius': 4.5,\n              'circle-color': '#e11d48',\n              'circle-stroke-color': '#ffffff',\n              'circle-stroke-width': 1.5,\n            }}\n          />\n          <Layer\n            id=\"gebied-ondiepste-label\"\n            type=\"symbol\"\n            layout={{\n              'text-field': ['get', 'label'],\n              'text-size': 12,\n              'text-offset': [0, 1.2],\n              'text-anchor': 'top',\n              'text-allow-overlap': false,\n            }}\n            paint={{\n              'text-color': '#9f1239',\n              'text-halo-color': '#ffffff',\n              'text-halo-width': 1.5,\n            }}\n          />\n        </Source>\n      )}\n    </>\n  );\n}\n","/**\n * Omgeving actueel als rasterlagen onder de peilingen (ontwerp 2026-10-14, §5).\n *\n * Eén laag per gemonteerde bron via het bestaande `pngtiles://`-protocol, alleen\n * de gekozen maand en zijn buren (zoals `PlaatmigratieLagen`), plus de bron die nu\n * zichtbaar is (ook bij een wissel van referentievlak). Volle kleur, geen\n * overgang: de vorige blijft op dekking 1 tot de kaart de nieuwe bron geladen\n * meldt. Die vraag stelt de kaart op elke `tik` van de hartslag in `MapView`\n * (`idleTik`), nooit op `idle`.\n *\n * Tellers (alleen met de tijdlijnmeting aan): `omgeving-wissel` meet van een nieuw\n * doel tot het geladen is; `omgeving-leeg` telt stappen zonder omgeving terwijl er\n * wel kaarten zijn. Fouten in een omgevingsbron telt `MapView` (`omgeving-fout`).\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { Layer, Source } from 'react-map-gl/maplibre';\n\nimport {\n  omgevingBronId,\n  teMonteren,\n  volgendeVooruit,\n  volgendeZichtbaar,\n  type OmgevingKaart,\n  type OmgevingVariant,\n} from '@/js/lib/omgeving';\nimport { startMeting, tel } from '@/js/lib/tijdlijn-meting';\n\ninterface Props {\n  kaarten: OmgevingKaart[];\n  doel: OmgevingKaart | null;\n  variant: OmgevingVariant;\n  tik: number;\n  isGeladen: (bronId: string) => boolean;\n  beforeId: string;\n  /** De peilingen zijn klaar en de kaart staat stil: de overige maanden mogen laden. */\n  vooruitladen?: boolean;\n}\n\n// Per hartslag zoveel maanden erbij, zodat de omgeving de peilingen niet verdringt.\nconst VOORUIT_PER_TIK = 2;\n\nexport default function OmgevingLagen({\n  kaarten,\n  doel,\n  variant,\n  tik,\n  isGeladen,\n  beforeId,\n  vooruitladen = false,\n}: Props) {\n  const doelId = doel ? omgevingBronId(doel, variant) : null;\n  const [zichtbaar, setZichtbaar] = useState<string | null>(doelId);\n  // Maanden die op de achtergrond klaargezet zijn blijven gemonteerd, zoals de peilingen.\n  const [vooruit, setVooruit] = useState<string[]>([]);\n  const montage = teMonteren(kaarten, doel, zichtbaar, variant, vooruit);\n  const wissel = useRef<{ id: string; stop: (status?: string) => void } | null>(null);\n  const heeftKaarten = kaarten.length > 0;\n\n  // Staat vóór het effect hieronder: bij een nieuw doel eerst de meting starten.\n  useEffect(() => {\n    if (!doelId) {\n      if (heeftKaarten) tel('omgeving-leeg');\n      return;\n    }\n    const stop = startMeting('omgeving-wissel', { bron: doelId });\n    wissel.current = { id: doelId, stop };\n    return () => {\n      if (wissel.current?.id === doelId) {\n        wissel.current = null;\n        stop('afgebroken');\n      }\n    };\n  }, [doelId, heeftKaarten]);\n\n  useEffect(() => {\n    setZichtbaar((huidig) => volgendeZichtbaar(huidig, doelId, isGeladen));\n    const w = wissel.current;\n    if (w && isGeladen(w.id)) {\n      wissel.current = null;\n      w.stop();\n    }\n  }, [doelId, tik, isGeladen]);\n\n  const montageIds = montage.map((m) => m.id).join(' ');\n  useEffect(() => {\n    if (!vooruitladen) return;\n    const ids = montageIds.split(' ').filter(Boolean);\n    if (!ids.every((id) => isGeladen(id))) return;\n    const erbij = volgendeVooruit(kaarten, doel, variant, new Set(ids), VOORUIT_PER_TIK);\n    // Ook wat nu gemonteerd is vasthouden: anders valt de buurmaand van nu weg bij een stap.\n    if (erbij.length) setVooruit((v) => [...new Set([...v, ...ids, ...erbij])]);\n    // Alleen op de hartslag: zo komt er per tik hoogstens een stapje bij.\n  }, [tik, vooruitladen]);\n\n  return (\n    <>\n      {montage.map((m) => (\n        <Source key={m.id} id={m.id} type=\"raster\" url={`pngtiles://${m.url}`} tileSize={256}>\n          <Layer\n            id={`${m.id}-laag`}\n            type=\"raster\"\n            beforeId={beforeId}\n            paint={{\n              'raster-opacity': m.id === zichtbaar ? 1 : 0,\n              'raster-opacity-transition': { duration: 0, delay: 0 },\n              'raster-fade-duration': 0,\n            }}\n          />\n        </Source>\n      ))}\n    </>\n  );\n}\n","import { pointInFeature } from './cross-section';\n\n/**\n * Wat er op een punt van de kaart ligt: een baggervak, of een van de vormen\n * eromheen (randstrook, talud, passeerstrook) of een stortlocatie.\n *\n * Een vakpolygoon is alleen de geul zelf; de brede blauwe band op de kaart is\n * vak + randstroken + taluds + passeerstroken, en die zijn allemaal gepeild.\n * Een klik op die band hoort dus het vak te noemen waar hij bij hoort, niet\n * \"buiten de vakken\" (Jelle, 15-10-2026).\n */\n\nexport type KaartObjectSoort = 'vak' | 'randstrook' | 'talud' | 'passeerstrook' | 'stortlocatie';\n\nexport interface KaartObject {\n  soort: KaartObjectSoort;\n  /** Code van de vorm zelf: \"C3\", \"R-C3\", \"T-C3N\", \"C3N\", \"P1\". */\n  code: string;\n  /** Leesbare naam: \"C3N\", \"Stortlocatie P1\". */\n  naam: string;\n  /** Het baggervak waar de vorm bij hoort; null bij een stortlocatie. */\n  vak: string | null;\n  /** Eigen ontwerpdieptes van de vorm in m NAP, als die er zijn. */\n  niveaus: { ngd: number | null; ivd: number | null; ohd: number | null } | null;\n  /** Alleen bij een stortlocatie. */\n  oppervlakM2: number | null;\n  omschrijving: string | null;\n}\n\nexport interface KaartObjectBronnen {\n  baggervakken: GeoJSON.FeatureCollection | null;\n  randstroken?: GeoJSON.FeatureCollection | null;\n  taluds?: GeoJSON.FeatureCollection | null;\n  passeerstroken?: GeoJSON.FeatureCollection | null;\n  stortlocaties?: GeoJSON.FeatureCollection | null;\n}\n\n/**\n * De OverlaySerializer nest de eigen JSON van het model onder\n * `properties.properties` (zie resolveOverlay in cross-section.ts).\n */\nfunction binnen(feature: GeoJSON.Feature): Record<string, unknown> {\n  return (feature.properties?.properties as Record<string, unknown> | undefined) ?? {};\n}\n\nfunction getalOfNull(waarde: unknown): number | null {\n  return typeof waarde === 'number' && Number.isFinite(waarde) ? waarde : null;\n}\n\nfunction tekstOfNull(waarde: unknown): string | null {\n  return typeof waarde === 'string' && waarde.trim() !== '' ? waarde : null;\n}\n\n/**\n * Randstroken dragen het vak alleen in hun code (\"R-C3\"); passeerstroken en\n * taluds hebben het als eigenschap.\n */\nfunction vakVanOverlay(feature: GeoJSON.Feature): string | null {\n  const eigen = tekstOfNull(binnen(feature).baggervak);\n  if (eigen) return eigen;\n  const code = tekstOfNull(feature.properties?.code);\n  return code ? code.replace(/^[RTV]-/, '') : null;\n}\n\nfunction overlayObject(soort: KaartObjectSoort, feature: GeoJSON.Feature): KaartObject {\n  const p = binnen(feature);\n  const code = tekstOfNull(feature.properties?.code) ?? '';\n  return {\n    soort,\n    code,\n    naam: tekstOfNull(feature.properties?.name) ?? code,\n    vak: soort === 'stortlocatie' ? null : vakVanOverlay(feature),\n    niveaus:\n      soort === 'stortlocatie'\n        ? null\n        : { ngd: getalOfNull(p.ngd), ivd: getalOfNull(p.ivd), ohd: getalOfNull(p.ohd) },\n    oppervlakM2: soort === 'stortlocatie' ? getalOfNull(p.area_m2) : null,\n    omschrijving: tekstOfNull(p.description),\n  };\n}\n\nfunction eerste(\n  lng: number,\n  lat: number,\n  collectie: GeoJSON.FeatureCollection | null | undefined\n): GeoJSON.Feature | null {\n  return collectie?.features.find((f) => f.geometry && pointInFeature(lng, lat, f)) ?? null;\n}\n\n/**\n * Het object op dit punt. Volgorde van buiten naar binnen gelezen andersom:\n * het vak gaat voor, dan de 5 m-strook langs zijn rand, het talud, de\n * passeerstrook, en pas daarna een stortlocatie.\n */\nexport function objectVoorPunt(\n  lng: number,\n  lat: number,\n  bronnen: KaartObjectBronnen\n): KaartObject | null {\n  const vak = eerste(lng, lat, bronnen.baggervakken);\n  const code = tekstOfNull(vak?.properties?.code);\n  if (vak && code) {\n    return {\n      soort: 'vak',\n      code,\n      naam: code,\n      vak: code,\n      niveaus: {\n        ngd: getalOfNull(vak.properties?.ngd_level),\n        ivd: getalOfNull(vak.properties?.ivd_level),\n        ohd: getalOfNull(vak.properties?.ohd_level),\n      },\n      oppervlakM2: null,\n      omschrijving: null,\n    };\n  }\n  const volgorde: [KaartObjectSoort, GeoJSON.FeatureCollection | null | undefined][] = [\n    ['randstrook', bronnen.randstroken],\n    ['talud', bronnen.taluds],\n    ['passeerstrook', bronnen.passeerstroken],\n    ['stortlocatie', bronnen.stortlocaties],\n  ];\n  for (const [soort, collectie] of volgorde) {\n    const feature = eerste(lng, lat, collectie);\n    if (feature) return overlayObject(soort, feature);\n  }\n  return null;\n}\n\n/** Hoe de speld en het werkblad de plek noemen. */\nexport function plekNaam(object: KaartObject): string {\n  switch (object.soort) {\n    case 'vak':\n      return object.code;\n    case 'randstrook':\n      return `5 m-strook van ${object.vak}`;\n    case 'talud':\n      return `talud ${object.naam}`;\n    case 'passeerstrook':\n      return `passeerstrook ${object.naam}`;\n    case 'stortlocatie':\n      return object.naam.toLowerCase().startsWith('stortlocatie')\n        ? `stortlocatie ${object.code}`\n        : `stortlocatie ${object.naam}`;\n  }\n}\n\n/**\n * De stroken en taluds als één collectie voor de referentievlakken\n * (sampleReferenceLevelsAlongLine), met het vak altijd als eigenschap: een\n * randstrook kent het alleen uit zijn code, en zonder vak valt het punt terug\n * op één vast niveau in plaats van het vlak uit het referentieraster.\n */\nexport function strokenVoorReferentie(\n  bronnen: KaartObjectBronnen\n): GeoJSON.FeatureCollection | null {\n  const features = [bronnen.randstroken, bronnen.taluds, bronnen.passeerstroken].flatMap(\n    (collectie) =>\n      (collectie?.features ?? []).map((f) => ({\n        ...f,\n        properties: {\n          ...f.properties,\n          properties: { ...binnen(f), baggervak: vakVanOverlay(f) },\n        },\n      }))\n  );\n  return features.length > 0 ? { type: 'FeatureCollection', features } : null;\n}\n","import { locationValues } from '@geomatico/maplibre-cog-protocol';\n\nimport { sampleReferenceLevelsAlongLine } from './cross-section';\nimport {\n  objectVoorPunt,\n  plekNaam,\n  strokenVoorReferentie,\n  type KaartObjectBronnen,\n} from './kaart-object';\n\nimport type { ReferenceLevel } from './depth-colors';\nimport type { OmgevingBron } from './omgeving';\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\n/**\n * Diepte-uitlezing op een punt van de kaart (#115; sinds 15-10-2026 bij een klik).\n *\n * Leest één pixel uit de bathymetrie-COG van het vak waarin het punt ligt,\n * plus de lokale referentievlakken op datzelfde punt. Dat laatste is nodig\n * omdat de kaart in de verschilweergave in delta-kleuren staat: een uitlezing\n * die alleen NAP toont zou dan iets anders zeggen dan wat je ziet.\n */\n\nexport interface DepthReading {\n  /**\n   * Vak waar het punt bij hoort: het vak zelf, of het vak van de randstrook,\n   * het talud of de passeerstrook waarin het ligt. Null buiten dat alles.\n   */\n  vakCode: string | null;\n  /** Hoe de plek heet (\"C3\", \"passeerstrook C3N\", \"stortlocatie P1\"); null erbuiten. */\n  plek: string | null;\n  /** Bodemhoogte in m NAP, of null buiten het raster of op een nodata-pixel. */\n  depthNap: number | null;\n  /** Lokaal referentievlak in m NAP op ditzelfde punt. */\n  referenceNap: number | null;\n  /**\n   * Afstand tot het referentievlak in meters. Positief betekent ondieper dan\n   * het vlak, dus in de richting waar gebaggerd moet worden.\n   */\n  deltaM: number | null;\n  /** σ in meter van de omgeving op dit punt; null op een peilingpixel. */\n  onzekerheid: number | null;\n  /** Waar de omgeving hier laatst gezien is; null op een peilingpixel. */\n  bron: OmgevingBron | null;\n  /** De omgeving is een verwachting (voorspellingsstap). */\n  verwacht: boolean;\n}\n\nexport const LEEG_READING: DepthReading = {\n  vakCode: null,\n  plek: null,\n  depthNap: null,\n  referenceNap: null,\n  deltaM: null,\n  onzekerheid: null,\n  bron: null,\n  verwacht: false,\n};\n\n/** De omgevingskaart van deze stap, voor de muis (ontwerp 2026-10-14, §5). */\nexport interface OmgevingLezing {\n  diepteUrl: string;\n  /** Drie banden: trap, σ in cm (255 = 2,55 m of meer), bronindex; nodata 0. */\n  zekerheidUrl: string;\n  bronnen: OmgevingBron[];\n  verwacht: boolean;\n}\n\ntype OmgevingDeel = Pick<DepthReading, 'depthNap' | 'onzekerheid' | 'bron' | 'verwacht'>;\n\nasync function leesOmgeving(\n  omgeving: OmgevingLezing,\n  lng: number,\n  lat: number,\n  zoom: number\n): Promise<OmgevingDeel | null> {\n  try {\n    const punt = { latitude: lat, longitude: lng };\n    const [diepte] = await locationValues(omgeving.diepteUrl, punt, zoom);\n    if (diepte == null || !Number.isFinite(diepte)) return null;\n    const [, sigmaCm, bronIndex] = await locationValues(omgeving.zekerheidUrl, punt, zoom);\n    return {\n      depthNap: diepte,\n      onzekerheid: Number.isFinite(sigmaCm) ? sigmaCm / 100 : null,\n      bron: Number.isFinite(bronIndex) ? (omgeving.bronnen[bronIndex] ?? null) : null,\n      verwacht: omgeving.verwacht,\n    };\n  } catch {\n    return null;\n  }\n}\n\nexport interface ReadDepthOptions {\n  lng: number;\n  lat: number;\n  /** Zichtbare peilingen; er wordt gezocht op `baggervak_code`. */\n  datasets: ReadonlyArray<SurveyDataset>;\n  baggervakken: GeoJSON.FeatureCollection | null;\n  referenceLevel: ReferenceLevel;\n  /** Levert de SAS-URL bij een dataset-id. */\n  getCogUrl: (id: number) => Promise<string | null>;\n  zoom?: number;\n  /** De omgeving van deze stap; gelezen buiten een vak of waar de peiling leeg is. */\n  omgeving?: OmgevingLezing | null;\n  /** Stroken, taluds en stortlocaties rond de vakken, om de plek te benoemen. */\n  vormen?: Omit<KaartObjectBronnen, 'baggervakken'>;\n}\n\nexport async function readDepthAtPoint({\n  lng,\n  lat,\n  datasets,\n  baggervakken,\n  referenceLevel,\n  getCogUrl,\n  zoom = 14,\n  omgeving = null,\n  vormen = {},\n}: ReadDepthOptions): Promise<DepthReading> {\n  // Een passeerstrook of talud hoort bij een vak en is in dezelfde peiling\n  // gemeten: lees daar de peiling en het referentievlak van dat vak.\n  const object = objectVoorPunt(lng, lat, { baggervakken, ...vormen });\n  const vakCode = object?.vak ?? null;\n  const plek = object ? plekNaam(object) : null;\n\n  const dataset = vakCode ? datasets.find((d) => d.baggervak_code === vakCode) : undefined;\n  const leesPeiling = async (id: number): Promise<number | null> => {\n    const url = await getCogUrl(id);\n    if (!url) return null;\n    try {\n      const waarden = await locationValues(url, { latitude: lat, longitude: lng }, zoom);\n      const rauw = waarden.length > 0 ? waarden[0] : null;\n      return rauw != null && Number.isFinite(rauw) ? rauw : null;\n    } catch {\n      // Een mislukte pixel-read is normaal aan de rand van een raster of bij\n      // extreme zoom; dan is er simpelweg geen peilingdiepte.\n      return null;\n    }\n  };\n  let depthNap: number | null = dataset ? await leesPeiling(dataset.id) : null;\n  const vanPeiling = depthNap != null;\n\n  let extra: Omit<OmgevingDeel, 'depthNap'> = { onzekerheid: null, bron: null, verwacht: false };\n  if (depthNap == null && omgeving) {\n    const o = await leesOmgeving(omgeving, lng, lat, zoom);\n    if (o) {\n      depthNap = o.depthNap;\n      extra = { onzekerheid: o.onzekerheid, bron: o.bron, verwacht: o.verwacht };\n    }\n  }\n\n  if (!vakCode || referenceLevel === 'nap' || depthNap == null) {\n    return { vakCode, plek, depthNap, referenceNap: null, deltaM: null, ...extra };\n  }\n\n  // Verschilkaart: hetzelfde punt in de vorige peiling van het vak. Alleen op\n  // een peilingpixel; de omgeving is geen peiling en heeft geen vorige.\n  if (referenceLevel === 'verschil') {\n    const vorigeId = vanPeiling ? dataset?.vorige_peiling_id : null;\n    const vorige = vorigeId != null ? await leesPeiling(vorigeId) : null;\n    return {\n      vakCode,\n      plek,\n      depthNap,\n      referenceNap: vorige,\n      deltaM: vorige != null ? depthNap - vorige : null,\n      ...extra,\n    };\n  }\n\n  // LAT staat als één getal op het baggervak en niet in de PolygonZ-overlays:\n  // het verloopt binnen een vak maar een paar centimeter (#114). Het hoeft dus\n  // niet per punt uit een raster gehaald te worden, en dat kan ook niet.\n  if (referenceLevel === 'lat') {\n    const feature = baggervakken?.features.find((f) => f.properties?.code === vakCode);\n    const latLevel = (feature?.properties?.lat_level as number | null | undefined) ?? null;\n    return {\n      vakCode,\n      plek,\n      depthNap,\n      referenceNap: latLevel,\n      deltaM: latLevel != null ? depthNap - latLevel : null,\n      ...extra,\n    };\n  }\n\n  const [referentie] = await sampleReferenceLevelsAlongLine(\n    [{ lng, lat }],\n    baggervakken,\n    strokenVoorReferentie({ baggervakken, ...vormen }),\n    zoom\n  );\n  const referenceNap = referentie?.[referenceLevel] ?? null;\n\n  return {\n    vakCode,\n    plek,\n    depthNap,\n    referenceNap,\n    deltaM: referenceNap != null ? depthNap - referenceNap : null,\n    ...extra,\n  };\n}\n","/**\n * Dieptecijfers als kaartlaag (#114, WP2).\n *\n * De server levert per peiling een lijst punten `[x, y, waarde]` in EPSG:28992\n * (zie `surveys/dieptecijfers.py`). Hier worden die omgezet naar GeoJSON met\n * een kant-en-klaar label, want MapLibre wil WGS84 en een tekstveld.\n *\n * Uitdunnen gebeurt niet hier maar in de kaart zelf: een symbol-layer met\n * botsingsdetectie laat alleen de labels staan die passen. De `sortKey` zorgt\n * dat bij een botsing het ondiepste cijfer wint, zodat het kaartbeeld bij\n * uitzoomen naar de ondieptes toe kruipt in plaats van naar een willekeurige\n * greep. Dat is ook wat CIV par. 8.1 bedoelt met een aantal cijfers dat van de\n * schaal afhangt.\n */\n\nimport { rdToWgs84 } from './projections';\n\nimport type { ReferenceLevel } from './depth-colors';\n\n/** Eén punt zoals de server het levert: x, y in RD, plus het te tonen getal. */\nexport type DieptecijferPunt = number[];\n\nexport interface DieptecijferProps {\n  /** Het getal zelf, in meters. */\n  waarde: number;\n  /** Kant-en-klaar label, bv. \"-15,6\" of \"+0,3\". */\n  label: string;\n  /** Lager = belangrijker bij een botsing. */\n  sortKey: number;\n  /** Staat dit cijfer boven het referentievlak (dus te ondiep)? */\n  boven: boolean;\n}\n\n/**\n * Label voor één cijfer.\n *\n * In NAP is het de diepte zelf en die is negatief; het minteken hoort er dan\n * bij, want een kaart die \"15,6\" toont naast een legenda in m NAP nodigt uit\n * tot verkeerd lezen. Bij een referentievlak krijgt het getal een expliciet\n * teken, zodat \"boven het vlak\" niet van de kleur alleen hoeft te komen.\n */\nexport function maakLabel(waarde: number, referentie: ReferenceLevel): string {\n  const tekst = Math.abs(waarde).toLocaleString('nl-NL', {\n    minimumFractionDigits: 1,\n    maximumFractionDigits: 1,\n  });\n  // NAP en LAT zijn een peil: het cijfer ís de diepte en die is negatief. Bij\n  // een ontwerpdiepte krijgt het getal een expliciet teken, want daar gaat het\n  // om de kant waar de bodem op zit.\n  if (referentie === 'nap' || referentie === 'lat') {\n    return waarde < 0 ? `-${tekst}` : tekst;\n  }\n  // -0,0 is verwarrend: dat leest als \"onder het vlak\" terwijl het nul is.\n  if (tekst === '0,0') return '0,0';\n  return waarde > 0 ? `+${tekst}` : `-${tekst}`;\n}\n\nexport function dieptecijfersGeoJSON(\n  punten: ReadonlyArray<DieptecijferPunt>,\n  referentie: ReferenceLevel\n): GeoJSON.FeatureCollection {\n  const features: GeoJSON.Feature[] = [];\n\n  for (const punt of punten) {\n    if (punt.length < 3) continue;\n    const [x, y, waarde] = punt;\n    if (!Number.isFinite(waarde)) continue;\n\n    const [lng, lat] = rdToWgs84(x, y);\n    const props: DieptecijferProps = {\n      waarde,\n      label: maakLabel(waarde, referentie),\n      // Ondiep = hoge waarde = lage sortKey = wint de botsing.\n      sortKey: -waarde,\n      boven: referentie !== 'nap' && referentie !== 'lat' && waarde > 0,\n    };\n\n    features.push({\n      type: 'Feature',\n      properties: props as unknown as GeoJSON.GeoJsonProperties,\n      geometry: { type: 'Point', coordinates: [lng, lat] },\n    });\n  }\n\n  return { type: 'FeatureCollection', features };\n}\n\n/**\n * Tekstkleur per cijfer.\n *\n * Twee kleuren, geen derde schaal. De kaart kleurt de bodem al volgens de\n * RWS-indeling; een cijfer dat diezelfde twaalf klassen nog eens dunnetjes\n * overdoet leest slechter, niet beter. Wat het cijfer wél moet zeggen is of\n * het boven het referentievlak uitkomt, want dat is de grens waar naar\n * gehandeld wordt.\n */\nexport const DIEPTECIJFER_KLEUR_BOVEN = '#b91c1c';\nexport const DIEPTECIJFER_KLEUR_ONDER = '#1f2937';\n\nexport function dieptecijferKleurExpressie(): unknown[] {\n  return ['case', ['get', 'boven'], DIEPTECIJFER_KLEUR_BOVEN, DIEPTECIJFER_KLEUR_ONDER];\n}\n","/**\n * Dieptecijfers per zichtbare peiling ophalen (#114, WP2).\n *\n * Eén aanvraag per peiling, want de cijfers worden uit dezelfde COG gelezen\n * die de kaart ook rendert en die staat per peiling in de blob-opslag. Het\n * antwoord wordt gecachet op (peiling, referentievlak): de tijdlijn en het\n * lagenpaneel schuiven vaak heen en weer, en de uitkomst verandert daar niet\n * van.\n *\n * De laag staat standaard uit. Elke aanvraag kost aan de serverkant een\n * blob-download, en dat is te duur om ongevraagd te doen voor iedereen die de\n * kaart opent.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { DieptecijfersResponse, SurveyDataset } from '@/js/api/types.gen';\nimport type { ReferenceLevel } from '@/js/lib/depth-colors';\nimport type { DieptecijferPunt } from '@/js/lib/dieptecijfers';\n\nimport { apiDatasetsDieptecijfersRetrieve } from '@/js/api';\nimport { dieptecijfersGeoJSON } from '@/js/lib/dieptecijfers';\n\ninterface Uitkomst {\n  punten: DieptecijferPunt[];\n  perPixelReferentie: boolean;\n  afgekapt: boolean;\n}\n\nconst cache = new Map<string, Uitkomst>();\nconst inFlight = new Map<string, Promise<Uitkomst | null>>();\n\nfunction cacheKey(datasetId: number, referentie: ReferenceLevel): string {\n  return `${datasetId}|${referentie}`;\n}\n\nasync function haalCijfers(\n  datasetId: number,\n  referentie: ReferenceLevel\n): Promise<Uitkomst | null> {\n  const key = cacheKey(datasetId, referentie);\n  const cached = cache.get(key);\n  if (cached) return cached;\n  const bezig = inFlight.get(key);\n  if (bezig) return bezig;\n\n  const promise = apiDatasetsDieptecijfersRetrieve({\n    path: { id: datasetId },\n    query: { referentie },\n  })\n    .then(({ data }) => {\n      const antwoord = data as DieptecijfersResponse | undefined;\n      if (!antwoord) return null;\n      const uitkomst: Uitkomst = {\n        punten: antwoord.punten ?? [],\n        perPixelReferentie: antwoord.per_pixel_referentie,\n        afgekapt: antwoord.afgekapt,\n      };\n      cache.set(key, uitkomst);\n      return uitkomst;\n    })\n    .catch(() => null)\n    .finally(() => inFlight.delete(key));\n\n  inFlight.set(key, promise);\n  return promise;\n}\n\nexport function clearDieptecijfersCache(): void {\n  cache.clear();\n  inFlight.clear();\n}\n\nexport interface DieptecijfersResultaat {\n  geojson: GeoJSON.FeatureCollection;\n  laden: boolean;\n  /** Aantal peilingen waarvoor het cijfer tegen een scalair niveau is gerekend. */\n  zonderReferentieRaster: number;\n  /** Aantal peilingen waarvan de cijferlijst is afgekapt. */\n  afgekapt: number;\n}\n\nconst LEEG: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: [] };\n\nexport function useDieptecijfers({\n  datasets,\n  referentie,\n  enabled,\n}: {\n  datasets: ReadonlyArray<SurveyDataset>;\n  referentie: ReferenceLevel;\n  enabled: boolean;\n}): DieptecijfersResultaat {\n  const [uitkomsten, setUitkomsten] = useState<Uitkomst[]>([]);\n  const [laden, setLaden] = useState(false);\n  // Alleen het laatst gestarte verzoek mag de state nog zetten. Zonder dit kan\n  // een trage aanvraag van een vorige datum over een nieuwere heen vallen.\n  const generatie = useRef(0);\n\n  const ids = useMemo(() => datasets.map((ds) => ds.id).join(','), [datasets]);\n\n  useEffect(() => {\n    if (!enabled || datasets.length === 0) {\n      setUitkomsten([]);\n      setLaden(false);\n      return;\n    }\n\n    const mijn = ++generatie.current;\n    setLaden(true);\n\n    Promise.all(datasets.map((ds) => haalCijfers(ds.id, referentie))).then((resultaten) => {\n      if (generatie.current !== mijn) return;\n      setUitkomsten(resultaten.filter((r): r is Uitkomst => r !== null));\n      setLaden(false);\n    });\n    // `ids` staat in de dependencies in plaats van `datasets`, omdat de\n    // aanroeper elke render een nieuwe array kan doorgeven en het effect dan\n    // elke keer opnieuw zou lopen. Het effect gebruikt van `datasets` alleen\n    // de ids, dus zodra die gelijk blijven verandert de uitkomst niet.\n  }, [ids, referentie, enabled]);\n\n  const geojson = useMemo(() => {\n    if (!enabled || uitkomsten.length === 0) return LEEG;\n    const punten = uitkomsten.flatMap((u) => u.punten);\n    return dieptecijfersGeoJSON(punten, referentie);\n  }, [uitkomsten, referentie, enabled]);\n\n  return {\n    geojson,\n    laden,\n    zonderReferentieRaster:\n      referentie === 'nap' ? 0 : uitkomsten.filter((u) => !u.perPixelReferentie).length,\n    afgekapt: uitkomsten.filter((u) => u.afgekapt).length,\n  };\n}\n","/**\n * Dieptecontouren als kaartlaag (#114, WP2).\n *\n * De server levert per peiling een lijst hoogtelijnen, elk met één niveau en\n * een reeks punten in EPSG:28992 (zie `surveys/dieptecontouren.py`). Hier\n * worden die omgezet naar GeoJSON-lijnen met een kant-en-klaar label.\n *\n * Contouren horen alleen bij een peil (NAP, LAT). Bij een ontwerpdiepte tekent\n * de kaart al gekleurde klassen en zou een lijn op de klassegrens alleen\n * herhalen wat de kleur al zegt.\n */\n\nimport { rdToWgs84 } from './projections';\n\nimport type { ReferenceLevel } from './depth-colors';\nimport type { Dieptecontour } from '@/js/api/types.gen';\n\nexport interface DieptecontourProps {\n  /** Het niveau van deze lijn, in meters ten opzichte van het gekozen peil. */\n  niveau: number;\n  /** Kant-en-klaar label, bv. \"-15,5\". */\n  label: string;\n  /**\n   * Een hele meter. Die worden dikker getekend en winnen bij een botsing, zoals\n   * op een topografische kaart: dan is er ook uitgezoomd nog een lijn met een\n   * getal in beeld in plaats van een wirwar zonder houvast.\n   */\n  heel: boolean;\n  /** Lager = belangrijker bij een botsing. */\n  sortKey: number;\n}\n\n/**\n * Verticale afstand tussen twee hoogtelijnen, in meters.\n *\n * Staat bewust niet in de UI. Het interval is een van de beslispunten die nog\n * bij RWS liggen (#114): CIV noemt er geen, en of een halve meter de juiste\n * keuze is hangt af van wat zij op de kaart willen zien. Zolang dat niet\n * beslist is, is een knop een suggestie dat de keuze al gemaakt is.\n */\nexport const CONTOUR_INTERVAL_M = 0.5;\n\n/**\n * De pixelmaat waarop de server de lijnen afleidt, in meters.\n *\n * Staat hier alleen om het in de uitleg te kunnen noemen: een contour is een\n * gegeneraliseerd product en een dieptecijfer niet, en die twee staan op\n * dezelfde kaart. Het gezaghebbende ondiepe getal is het cijfer, niet de lijn.\n */\nexport const CONTOUR_GENERALISATIE_M = 2;\n\n/** Alleen een peil krijgt contouren; zie de moduledocstring. */\nexport function heeftContouren(referentie: ReferenceLevel): boolean {\n  return referentie === 'nap' || referentie === 'lat';\n}\n\n/**\n * Label voor één hoogtelijn.\n *\n * Het niveau is een diepte ten opzichte van een peil en dus negatief; het\n * minteken hoort erbij, want een kaart die \"15,5\" toont naast een legenda in\n * m NAP nodigt uit tot verkeerd lezen. Hele meters krijgen geen decimaal,\n * omdat \"-15\" korter leest dan \"-15,0\" en er niets verloren gaat.\n */\nexport function maakContourLabel(niveau: number): string {\n  const heel = Math.abs(niveau % 1) < 1e-6;\n  const tekst = Math.abs(niveau).toLocaleString('nl-NL', {\n    minimumFractionDigits: heel ? 0 : 1,\n    maximumFractionDigits: heel ? 0 : 1,\n  });\n  return niveau < 0 ? `-${tekst}` : tekst;\n}\n\nexport function dieptecontourenGeoJSON(\n  lijnen: ReadonlyArray<Dieptecontour>\n): GeoJSON.FeatureCollection {\n  const features: GeoJSON.Feature[] = [];\n\n  for (const lijn of lijnen) {\n    if (!Number.isFinite(lijn.niveau) || lijn.punten.length < 2) continue;\n\n    const coordinates: [number, number][] = [];\n    for (const punt of lijn.punten) {\n      if (punt.length < 2) continue;\n      coordinates.push(rdToWgs84(punt[0], punt[1]));\n    }\n    if (coordinates.length < 2) continue;\n\n    const heel = Math.abs(lijn.niveau % 1) < 1e-6;\n    const props: DieptecontourProps = {\n      niveau: lijn.niveau,\n      label: maakContourLabel(lijn.niveau),\n      heel,\n      // Hele meters eerst, en daarbinnen de ondiepste lijn.\n      sortKey: (heel ? 0 : 1000) - lijn.niveau,\n    };\n\n    features.push({\n      type: 'Feature',\n      properties: props as unknown as GeoJSON.GeoJsonProperties,\n      geometry: { type: 'LineString', coordinates },\n    });\n  }\n\n  return { type: 'FeatureCollection', features };\n}\n\n/**\n * Lijndikte per contour.\n *\n * Hele meters dikker dan de tussenliggende lijnen. Dat is de indeling van een\n * topografische kaart: de dikke lijnen dragen het beeld, de dunne vullen in.\n */\nexport const CONTOUR_KLEUR = 'rgba(15, 23, 42, 0.7)';\nexport const CONTOUR_LABEL_KLEUR = '#0f172a';\n\nexport function contourDikteExpressie(): unknown[] {\n  return ['case', ['get', 'heel'], 1.4, 0.7];\n}\n","/**\n * Dieptecontouren per zichtbare peiling ophalen (#114, WP2).\n *\n * Zelfde opzet als `use-dieptecijfers`: één aanvraag per peiling, gecachet op\n * (peiling, peil, interval), en alleen als de laag aanstaat. Elke aanvraag kost\n * aan de serverkant een blob-download.\n *\n * Contouren bestaan alleen tegen een peil. Staat de kaart op een ontwerpdiepte,\n * dan wordt er niets opgehaald en blijft de laag leeg.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { DieptecontourenResponse, Dieptecontour, SurveyDataset } from '@/js/api/types.gen';\nimport type { ReferenceLevel } from '@/js/lib/depth-colors';\n\nimport { apiDatasetsDieptecontourenRetrieve } from '@/js/api';\nimport { dieptecontourenGeoJSON, heeftContouren } from '@/js/lib/dieptecontouren';\n\ninterface Uitkomst {\n  lijnen: Dieptecontour[];\n  afgekapt: boolean;\n}\n\nconst cache = new Map<string, Uitkomst>();\nconst inFlight = new Map<string, Promise<Uitkomst | null>>();\n\nfunction cacheKey(datasetId: number, referentie: ReferenceLevel, interval: number): string {\n  return `${datasetId}|${referentie}|${interval}`;\n}\n\nasync function haalContouren(\n  datasetId: number,\n  referentie: ReferenceLevel,\n  interval: number\n): Promise<Uitkomst | null> {\n  const key = cacheKey(datasetId, referentie, interval);\n  const cached = cache.get(key);\n  if (cached) return cached;\n  const bezig = inFlight.get(key);\n  if (bezig) return bezig;\n\n  const promise = apiDatasetsDieptecontourenRetrieve({\n    path: { id: datasetId },\n    query: { referentie, interval },\n  })\n    .then(({ data }) => {\n      const antwoord = data as DieptecontourenResponse | undefined;\n      if (!antwoord) return null;\n      const uitkomst: Uitkomst = {\n        lijnen: antwoord.lijnen ?? [],\n        afgekapt: antwoord.afgekapt,\n      };\n      cache.set(key, uitkomst);\n      return uitkomst;\n    })\n    .catch(() => null)\n    .finally(() => inFlight.delete(key));\n\n  inFlight.set(key, promise);\n  return promise;\n}\n\nexport function clearDieptecontourenCache(): void {\n  cache.clear();\n  inFlight.clear();\n}\n\nexport interface DieptecontourenResultaat {\n  geojson: GeoJSON.FeatureCollection;\n  laden: boolean;\n  /** Aantal peilingen waarvan de lijnenlijst is afgekapt. */\n  afgekapt: number;\n}\n\nconst LEEG: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: [] };\n\nexport function useDieptecontouren({\n  datasets,\n  referentie,\n  interval,\n  enabled,\n}: {\n  datasets: ReadonlyArray<SurveyDataset>;\n  referentie: ReferenceLevel;\n  interval: number;\n  enabled: boolean;\n}): DieptecontourenResultaat {\n  const [uitkomsten, setUitkomsten] = useState<Uitkomst[]>([]);\n  const [laden, setLaden] = useState(false);\n  // Alleen het laatst gestarte verzoek mag de state nog zetten.\n  const generatie = useRef(0);\n\n  const actief = enabled && heeftContouren(referentie);\n  const ids = useMemo(() => datasets.map((ds) => ds.id).join(','), [datasets]);\n\n  useEffect(() => {\n    if (!actief || datasets.length === 0) {\n      setUitkomsten([]);\n      setLaden(false);\n      return;\n    }\n\n    const mijn = ++generatie.current;\n    setLaden(true);\n\n    Promise.all(datasets.map((ds) => haalContouren(ds.id, referentie, interval))).then(\n      (resultaten) => {\n        if (generatie.current !== mijn) return;\n        setUitkomsten(resultaten.filter((r): r is Uitkomst => r !== null));\n        setLaden(false);\n      }\n    );\n    // `ids` in plaats van `datasets`: de aanroeper kan elke render een nieuwe\n    // array doorgeven, en van die array gebruikt het effect alleen de ids.\n  }, [ids, referentie, interval, actief]);\n\n  const geojson = useMemo(() => {\n    if (!actief || uitkomsten.length === 0) return LEEG;\n    return dieptecontourenGeoJSON(uitkomsten.flatMap((u) => u.lijnen));\n  }, [uitkomsten, actief]);\n\n  return {\n    geojson,\n    laden,\n    afgekapt: uitkomsten.filter((u) => u.afgekapt).length,\n  };\n}\n","/**\n * Convert EPSG:3857 (Web Mercator) coordinates to WGS84 (lng, lat) degrees.\n *\n * Uses the standard spherical Mercator inverse. The constant 20037508.34\n * is the half-circumference of the Earth at the equator in EPSG:3857 meters\n * (≈ π·R, where R = 6378137 m).\n */\nexport function mercatorToLngLat(x: number, y: number): [number, number] {\n  const lng = (x / 20037508.34) * 180;\n  let lat = (y / 20037508.34) * 180;\n  lat = (180 / Math.PI) * (2 * Math.atan(Math.exp((lat * Math.PI) / 180)) - Math.PI / 2);\n  return [lng, lat];\n}\n","/**\n * Welke peilingen liggen in het huidige kaartbeeld (#34, #20).\n *\n * De viewer zette tot nu toe alle peilingen van de gekozen maand als COG-source\n * op de kaart — tot veertig tegelijk, ook als je op één vak ingezoomd zat. Elke\n * datumstap verving die veertig dan allemaal. Door op de bbox te filteren wordt\n * een datumstap zo groot als wat je werkelijk ziet, en pas dán is vooruitladen\n * over de tijdlijn een begrensde som.\n *\n * De bbox-velden komen in EPSG:3857 uit de COG-writer; de kaartbounds in\n * graden. We rekenen de bbox om in plaats van andersom: de omrekening is\n * monotoon in beide assen, dus twee hoekpunten volstaan.\n */\n\nimport { mercatorToLngLat } from './mercator';\n\nexport interface KaartBounds {\n  west: number;\n  south: number;\n  east: number;\n  north: number;\n}\n\n/** Alleen de velden die voor het filter nodig zijn — houdt de functie testbaar. */\nexport interface DatasetMetBbox {\n  bbox_minx?: number | null;\n  bbox_miny?: number | null;\n  bbox_maxx?: number | null;\n  bbox_maxy?: number | null;\n}\n\n/**\n * Standaardmarge rond het beeld, als fractie van breedte en hoogte. Zonder\n * marge zou het vak aan de rand bij het eerste pixeltje pannen ontladen en bij\n * het terugpannen opnieuw moeten laden.\n */\nexport const BEELD_MARGE = 0.25;\n\nfunction metMarge(bounds: KaartBounds, marge: number): KaartBounds {\n  const dx = (bounds.east - bounds.west) * marge;\n  const dy = (bounds.north - bounds.south) * marge;\n  return {\n    west: bounds.west - dx,\n    east: bounds.east + dx,\n    south: bounds.south - dy,\n    north: bounds.north + dy,\n  };\n}\n\n/**\n * Overlapt een bbox in EPSG:3857 het kaartbeeld?\n *\n * @param bounds `null` zolang de kaart nog geen beeld heeft; dan telt alles mee.\n * @param marge  Fractie waarmee het beeld wordt opgerekt; zie {@link BEELD_MARGE}.\n */\nexport function bboxInBeeld(\n  minx: number,\n  miny: number,\n  maxx: number,\n  maxy: number,\n  bounds: KaartBounds | null,\n  marge: number = BEELD_MARGE\n): boolean {\n  if (!bounds) return true;\n  const beeld = metMarge(bounds, marge);\n  const [west, south] = mercatorToLngLat(minx, miny);\n  const [east, north] = mercatorToLngLat(maxx, maxy);\n  return east >= beeld.west && west <= beeld.east && north >= beeld.south && south <= beeld.north;\n}\n\n/**\n * Filter de peilingen op overlap met het kaartbeeld.\n *\n * @param bounds `null` zolang de kaart nog geen beeld heeft; dan blijft alles staan.\n * @param marge  Fractie waarmee het beeld wordt opgerekt; zie {@link BEELD_MARGE}.\n */\nexport function datasetsInBeeld<T extends DatasetMetBbox>(\n  datasets: readonly T[],\n  bounds: KaartBounds | null,\n  marge: number = BEELD_MARGE\n): T[] {\n  if (!bounds) return [...datasets];\n\n  return datasets.filter((ds) => {\n    const { bbox_minx, bbox_miny, bbox_maxx, bbox_maxy } = ds;\n    // Zonder bbox valt er niets te beoordelen. Laten staan: een peiling ten\n    // onrechte verbergen is erger dan er een te veel tekenen.\n    if (bbox_minx == null || bbox_miny == null || bbox_maxx == null || bbox_maxy == null) {\n      return true;\n    }\n    return bboxInBeeld(bbox_minx, bbox_miny, bbox_maxx, bbox_maxy, bounds, marge);\n  });\n}\n","/**\n * De overige datums, dichtstbijzijnde eerst — bij gelijke afstand die vooruit\n * in de tijd eerst, omdat je vaker naar recenter dan naar ouder stapt.\n *\n * Dit is dé volgorde van het wisselmechanisme: het klaarzetten van maanden\n * (use-maand-lagen), het vooruitladen en het klokje op de tijdlijnbalk\n * gebruiken allemaal dezelfde lijst, zodat wat de balk aankondigt ook echt\n * is wat de kaart als volgende doet.\n */\nexport function datumsOpAfstand(datums: string[], huidige: string): string[] {\n  const hier = datums.indexOf(huidige);\n  if (hier < 0) return [];\n  const uit: string[] = [];\n  for (let stap = 1; stap < datums.length; stap++) {\n    const na = hier + stap;\n    const voor = hier - stap;\n    if (na < datums.length) uit.push(datums[na]);\n    if (voor >= 0) uit.push(datums[voor]);\n  }\n  return uit;\n}\n","/** Prepare the entire timeline, nearest dates first, and keep prepared sources\n * mounted across date changes. After movement, admit months progressively again\n * so a new viewport does not launch the whole history's tile requests at once. */\nimport { setColorFunction } from '@geomatico/maplibre-cog-protocol';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport type { SurveyDataset } from '@/js/api';\nimport type { DatasetMetBbox, KaartBounds } from '@/js/lib/dataset-viewport';\nimport type { Colormap, ReferenceLevel } from '@/js/lib/depth-colors';\n\nimport { fetchDatasets, getDisplayCogUrl } from '@/js/hooks/use-datasets';\nimport { getReferenceCogUrl } from '@/js/hooks/use-reference-cog';\nimport { clearReferencePairing, setReferencePairing, setSpreiding } from '@/js/lib/bathycog-delta';\nimport { datasetsInBeeld } from '@/js/lib/dataset-viewport';\nimport { datumsOpAfstand } from '@/js/lib/datum-afstand';\nimport { configForReferenceLevel, makeColorFunction } from '@/js/lib/depth-colors';\nimport { kaartExperiment } from '@/js/lib/kaart-experiment';\nimport { logTijdlijn, meet, registreerDiagnose, startMeting, tel } from '@/js/lib/tijdlijn-meting';\n\nconst METADATA_MAANDEN = 24;\nconst RETRY_MS = 2000;\nconst MAX_POGINGEN = 3;\nconst PREPARED_TILES = kaartExperiment('kaartPreparedTiles', [1], 0) === 1;\n\n/** Zelfde vangnet als de kaart altijd al had: nooit meer sources per maand. */\nconst MAX_LAGEN_PER_MAAND = 40;\n\nexport interface MaandLaagEntry extends DatasetMetBbox {\n  tileUrls?: Record<string, string>;\n  id: number;\n  url: string;\n  refLevel: ReferenceLevel;\n}\n\nexport function laagSleutel(id: number, refLevel: ReferenceLevel): string {\n  return `${id}-${refLevel}`;\n}\n\nexport interface VakReferenties {\n  ngd: number | null;\n  ohd: number | null;\n  lat: number | null;\n  /** Maandspreiding σ (m) voor de kanskaart; leeg bij te weinig peilingparen. */\n  sigma?: number | null;\n}\n\ninterface MaandLagenOpties {\n  colormap?: Colormap;\n  clip?: boolean;\n  datums: string[];\n  huidigeDatum: string | null;\n  retentionMonths: number;\n  referenceLevel: ReferenceLevel;\n  beeld: KaartBounds | null;\n  /** Heartbeat; readiness separately controls admission of the next month. */\n  tik: number;\n  vooruitladen: boolean;\n  /** Resolve two dates ahead while admitted sources load; does not mount tiles. */\n  metadataVooruitladen?: boolean;\n  beweegt?: boolean;\n  enabled: boolean;\n  vakRefLookup: Record<string, VakReferenties>;\n}\n\nexport interface MaandLagenUit {\n  /** Alle gemonteerde lagen (unie over het maandvenster), stabiel gesleuteld. */\n  lagen: MaandLaagEntry[];\n  /** De sleutels (`laagSleutel`) die zichtbaar horen te zijn. */\n  actieveSleutels: Set<string>;\n  /** Datums die voor het huidige beeld klaarstaan — een stap ernaartoe is\n   * alleen een zichtbaarheidswissel. Voedt de stipjes op de tijdlijnbalk. */\n  klareDatums: string[];\n  /** Per klaarstaande datum de laagsleutels — zodat de kaart kan nagaan of\n   * die lagen hun tegels ook écht geladen hebben (klaargezet ≠ binnen). */\n  datumSleutels: Record<string, string[]>;\n  /** Staat er in het venster nog iets klaar te zetten? */\n  bezigMetKlaarzetten: boolean;\n  ladendeDatums: string[];\n  fout: string | null;\n}\n\n/** Het venster: de huidige maand plus de dichtstbijzijnde, tot de limiet. */\nexport function maandVenster(datums: string[], huidige: string, max = datums.length): string[] {\n  if (!datums.includes(huidige)) return [];\n  return [huidige, ...datumsOpAfstand(datums, huidige)].slice(0, max);\n}\n\n/**\n * Los de lagen voor één maand op: datasetlijst → COG-URL's →\n * kleurfunctie + referentiekoppeling. Zelfde volgorde-semantiek als het\n * oude COG-effect in MapView: de koppeling wordt pas aangepast als het\n * antwoord er is, zodat er geen venster bestaat waarin een gemonteerde\n * laag zonder referentie zit.\n */\nasync function resolveerMaand(\n  datum: string,\n  opties: {\n    retentionMonths: number;\n    referenceLevel: ReferenceLevel;\n    beeld: KaartBounds;\n    vakRefLookup: Record<string, VakReferenties>;\n    isGestopt: () => boolean;\n  }\n): Promise<{ entries: MaandLaagEntry[]; catalogus: SurveyDataset[] } | null> {\n  const { retentionMonths, referenceLevel, beeld, vakRefLookup, isGestopt } = opties;\n  // Een mislukte fetch is géén lege maand. Wie hier een lege lijst teruggeeft,\n  // slaat \"deze maand heeft niets\" op als vaststaand feit — en dan toont een\n  // latere stap naar die maand een lege kaart. Null = niet opslaan, zodat een\n  // volgende ronde het opnieuw probeert.\n  let alles: SurveyDataset[];\n  try {\n    alles = await meet('maand.datasetlijst', () => fetchDatasets(datum, retentionMonths), {\n      datum,\n    });\n  } catch {\n    return null;\n  }\n  if (isGestopt()) return null;\n\n  const zichtbaar = datasetsInBeeld(alles, beeld).slice(0, MAX_LAGEN_PER_MAAND);\n  logTijdlijn('maand.selectie', { datum, totaal: alles.length, inBeeld: zichtbaar.length });\n\n  const entries = await Promise.all(\n    zichtbaar.map(async (ds) => {\n      const url = await getDisplayCogUrl(ds.id);\n      if (isGestopt()) return null;\n      if (!url) throw new Error('COG URL niet beschikbaar');\n      const refs = vakRefLookup[ds.baggervak_code];\n      const dsConfig = configForReferenceLevel(\n        referenceLevel,\n        refs?.ngd ?? null,\n        // Geen IVD op de kaart (bestek 2026); de parameter blijft voor de tegelrenderer.\n        null,\n        refs?.ohd ?? null,\n        refs?.lat ?? null\n      );\n      // Altijd een legacy color-function registreren — fallback én NAP-pad.\n      setColorFunction(url, makeColorFunction(dsConfig));\n      // Los van de gekozen schaal: σ hoort bij het vak, niet bij de kleuren.\n      setSpreiding(url, refs?.sigma);\n\n      const refUrl = await tweedeCogUrl(ds, referenceLevel);\n      if (isGestopt()) return null;\n      if (refUrl) setReferencePairing(url, refUrl);\n      else clearReferencePairing(url);\n      return {\n        id: ds.id,\n        tileUrls: (ds as SurveyDataset & { display_tile_urls?: Record<string, string> })\n          .display_tile_urls,\n        url,\n        refLevel: referenceLevel,\n        bbox_minx: ds.bbox_minx,\n        bbox_miny: ds.bbox_miny,\n        bbox_maxx: ds.bbox_maxx,\n        bbox_maxy: ds.bbox_maxy,\n      };\n    })\n  );\n  if (isGestopt()) return null;\n  return { entries: entries.filter((e) => e !== null), catalogus: alles };\n}\n\n/**\n * De COG die per pixel van deze peiling wordt afgetrokken, of null.\n *\n * Bij een ontwerpdiepte het referentieraster van het vak. Bij de verschilkaart\n * de vorige peiling van hetzelfde vak, zoals de API die aanwijst (geen\n * termijn; leeg bij de eerste of een jaarpeiling): zonder vorige peiling tekent dat vak niets, en niet\n * stilletjes de diepte. Bij een peil (NAP, LAT) niets: dat kleurt de diepte zelf.\n *\n * Gedeeld met de aantoonpeilingen in `MapView`.\n */\nexport async function tweedeCogUrl(\n  ds: Pick<SurveyDataset, 'baggervak_code' | 'vorige_peiling_id'>,\n  referenceLevel: ReferenceLevel\n): Promise<string | null> {\n  if (referenceLevel === 'nap' || referenceLevel === 'lat') return null;\n  if (referenceLevel === 'verschil') {\n    return ds.vorige_peiling_id != null ? getDisplayCogUrl(ds.vorige_peiling_id) : null;\n  }\n  return getReferenceCogUrl(ds.baggervak_code, referenceLevel);\n}\n\n/** Waarvoor een oplossing is gemaakt; de sleutel voor lopende opvragen. */\nfunction configSleutel(\n  beeld: KaartBounds,\n  referenceLevel: ReferenceLevel,\n  retentionMonths: number\n): string {\n  return [beeld.west, beeld.south, beeld.east, beeld.north, referenceLevel, retentionMonths].join(\n    '|'\n  );\n}\n\n/** Een klaargezette maand, met de context waaronder dat gebeurde. */\ninterface MaandOplossing {\n  resolvedAt: number;\n  refLevel: ReferenceLevel;\n  retention: number;\n  beeld: KaartBounds;\n  entries: MaandLaagEntry[];\n  catalogus: SurveyDataset[];\n}\n\n/**\n * Dekt een eerdere oplossing het huidige beeld nog?\n *\n * De datasetlijst dekt de hele maand. Vergelijk de benodigde datasets met\n * de al opgeloste bronnen, zodat pannen binnen dezelfde peilingen geen\n * nieuwe voorbereidingsronde start. Een nieuwe dataset, verlopen metadata\n * of een gewijzigde referentie/retentie vereist wel een nieuwe oplossing.\n */\nfunction dekt(\n  oplossing: MaandOplossing,\n  referenceLevel: ReferenceLevel,\n  retentionMonths: number,\n  beeld: KaartBounds\n): boolean {\n  const opgelost = new Set(oplossing.entries.map((entry) => entry.id));\n  return (\n    Date.now() - oplossing.resolvedAt < 20 * 60 * 1000 &&\n    oplossing.refLevel === referenceLevel &&\n    oplossing.retention === retentionMonths &&\n    // The API lists the whole month. Crossing the old camera bounds does not\n    // invalidate resolved sources unless another dataset actually enters view.\n    datasetsInBeeld(oplossing.catalogus, beeld)\n      .slice(0, MAX_LAGEN_PER_MAAND)\n      .every((entry) => opgelost.has(entry.id))\n  );\n}\n\nexport function useMaandLagen(opties: MaandLagenOpties): MaandLagenUit {\n  const {\n    datums,\n    huidigeDatum,\n    retentionMonths,\n    referenceLevel,\n    beeld,\n    tik,\n    enabled,\n    vooruitladen,\n    metadataVooruitladen = false,\n    beweegt = false,\n    vakRefLookup,\n  } = opties;\n  const [perDatum, setPerDatum] = useState<Map<string, MaandOplossing>>(new Map());\n  const variant = `${referenceLevel}-${referenceLevel === 'nap' || referenceLevel === 'lat' ? 'continu' : (opties.colormap ?? 'discreet')}`;\n  const known = [...perDatum.values()].flatMap((v) => v.entries);\n  const preparedTiles =\n    PREPARED_TILES ||\n    (!opties.clip && known.length > 0 && known.every((e) => !!e.tileUrls?.[variant]));\n  const [revision, setRevision] = useState(0);\n  const bronContext = `${referenceLevel}|${retentionMonths}|${variant}|${!!opties.clip}|${enabled}`;\n  const isPakket = useCallback(\n    (entry: MaandLaagEntry) => !opties.clip && (PREPARED_TILES || !!entry.tileUrls?.[variant]),\n    [opties.clip, variant]\n  );\n  const [montage, setMontage] = useState({\n    context: '',\n    bronContext: '',\n    datums: new Set<string>(),\n    bewaard: new Set<string>(),\n  });\n  const actieveDatumRef = useRef<string | null>(null);\n  const epoch = useRef(0);\n  const jobs = useRef(new Map<string, { datum: string; background: boolean; epoch: number }>());\n  const failures = useRef(new Map<string, { attempts: number; retryAt: number }>());\n  const laatstGestarteTik = useRef(-1);\n  const sleutelNu = beeld ? configSleutel(beeld, referenceLevel, retentionMonths) : null;\n\n  // Date changes retain every prepared source (and its MapLibre tile textures).\n  // Movement starts a fresh admission round, while retaining metadata/RGBA caches.\n  useEffect(() => {\n    setMontage((old) => ({\n      context: sleutelNu ?? '',\n      bronContext,\n      datums: preparedTiles && old.bronContext === bronContext ? old.datums : new Set(),\n      bewaard: old.bronContext === bronContext ? old.bewaard : new Set(),\n    }));\n  }, [sleutelNu, bronContext, enabled, beweegt, preparedTiles]);\n\n  // Only already admitted package sources survive movement. TIFF-only dates\n  // still enter progressively; a mixed date retains its PNGs without admitting\n  // its unprepared TIFFs. Coverage is checked against the complete catalogue.\n  const pakketDatums = useMemo(() => {\n    if (!enabled || !beeld || montage.bronContext !== bronContext) return [];\n    return [...montage.bewaard].filter((datum) => {\n      const value = perDatum.get(datum);\n      return (\n        value &&\n        dekt(value, referenceLevel, retentionMonths, beeld) &&\n        datasetsInBeeld(value.entries, beeld).every(isPakket)\n      );\n    });\n  }, [enabled, beeld, montage, bronContext, perDatum, referenceLevel, retentionMonths, isPakket]);\n\n  // Only a viewport/config change or unmount invalidates a result. A heartbeat,\n  // date step, or promotion of an in-flight neighbour does not discard work.\n  useEffect(() => {\n    epoch.current++;\n    failures.current.clear();\n    return () => {\n      epoch.current++;\n    };\n  }, [sleutelNu, vakRefLookup, enabled]);\n\n  useEffect(() => {\n    if (!enabled || !beeld || !huidigeDatum || !sleutelNu) return;\n    const run = epoch.current;\n    const venster = maandVenster(datums, huidigeDatum);\n    const actueel = (datum: string) => {\n      const v = perDatum.get(datum);\n      return !!v && dekt(v, referenceLevel, retentionMonths, beeld);\n    };\n    const toegelaten = new Set([\n      ...(preparedTiles || montage.context === sleutelNu ? montage.datums : []),\n      ...pakketDatums,\n    ]);\n    const monteer = (datum: string) => {\n      setMontage((oud) => {\n        if (oud.context === sleutelNu && oud.bronContext === bronContext && oud.datums.has(datum))\n          return oud;\n        const datums = new Set(oud.context === sleutelNu ? oud.datums : []);\n        datums.add(datum);\n        const bewaard = new Set(oud.bronContext === bronContext ? oud.bewaard : []);\n        bewaard.add(datum);\n        return { context: sleutelNu, bronContext, datums, bewaard };\n      });\n    };\n    const jobKey = (datum: string) => `${run}|${datum}`;\n    const kanStarten = (datum: string) => {\n      const failure = failures.current.get(datum);\n      return (\n        !actueel(datum) &&\n        !jobs.current.has(jobKey(datum)) &&\n        (!failure || (failure.attempts < MAX_POGINGEN && Date.now() >= failure.retryAt))\n      );\n    };\n    const start = (datum: string, background: boolean, mount = true) => {\n      const key = jobKey(datum);\n      jobs.current.set(key, { datum, background, epoch: run });\n      setRevision((n) => n + 1);\n      const klaar = startMeting(background ? 'maand.achtergrond' : 'maand.actief', { datum, tik });\n      const gestopt = () => epoch.current !== run;\n      const mislukt = () => {\n        const attempts = (failures.current.get(datum)?.attempts ?? 0) + 1;\n        failures.current.set(datum, { attempts, retryAt: Date.now() + RETRY_MS });\n      };\n      resolveerMaand(datum, {\n        retentionMonths,\n        referenceLevel,\n        beeld,\n        vakRefLookup,\n        isGestopt: gestopt,\n      })\n        .then((resultaat) => {\n          if (gestopt()) {\n            klaar('cancelled');\n            return;\n          }\n          if (resultaat === null) {\n            klaar('error');\n            mislukt();\n            return;\n          }\n          klaar();\n          failures.current.delete(datum);\n          if (mount) monteer(datum);\n          setPerDatum((oud) => {\n            const nieuw = new Map(oud);\n            nieuw.delete(datum);\n            nieuw.set(datum, {\n              resolvedAt: Date.now(),\n              refLevel: referenceLevel,\n              retention: retentionMonths,\n              beeld,\n              ...resultaat,\n            });\n            while (nieuw.size > Math.max(METADATA_MAANDEN, datums.length))\n              nieuw.delete(nieuw.keys().next().value!);\n            return nieuw;\n          });\n        })\n        .catch(() => {\n          klaar(gestopt() ? 'cancelled' : 'error');\n          if (!gestopt()) mislukt();\n        })\n        .finally(() => {\n          jobs.current.delete(key);\n          if (!gestopt()) setRevision((n) => n + 1);\n        });\n    };\n\n    if (kanStarten(huidigeDatum)) start(huidigeDatum, false);\n    if (actueel(huidigeDatum) && !toegelaten.has(huidigeDatum)) monteer(huidigeDatum);\n    const backgroundRunning = [...jobs.current.values()].some(\n      (job) => job.epoch === run && job.background\n    );\n    if (\n      tik > 0 &&\n      vooruitladen &&\n      !beweegt &&\n      actueel(huidigeDatum) &&\n      laatstGestarteTik.current !== tik\n    ) {\n      const volgende = venster\n        .slice(1)\n        .filter(\n          (d) =>\n            (!toegelaten.has(d) || (preparedTiles && !actueel(d))) && (actueel(d) || kanStarten(d))\n        )\n        .slice(0, preparedTiles ? 4 : 1);\n      for (const datum of volgende) {\n        if (actueel(datum) || !backgroundRunning) {\n          laatstGestarteTik.current = tik;\n          if (actueel(datum)) monteer(datum);\n          else {\n            tel('bijmonteren: maand gestart');\n            start(datum, true);\n          }\n        }\n      }\n    }\n    // Metadata and raster I/O can overlap. Keep one metadata request running,\n    // at most two unmounted dates ahead, and keep source admission above gated\n    // by tile readiness. This cannot flood MapLibre with all-history tiles.\n    if (\n      metadataVooruitladen &&\n      tik > 0 &&\n      !beweegt &&\n      actueel(huidigeDatum) &&\n      ![...jobs.current.values()].some((j) => j.epoch === run && j.background)\n    ) {\n      const volgende = venster\n        .slice(1)\n        .filter((d) => !toegelaten.has(d))\n        .slice(0, 2)\n        .find(kanStarten);\n      if (volgende) start(volgende, true, false);\n    }\n    return registreerDiagnose('maandLagen', () => ({\n      tik,\n      venster,\n      klaar: [...perDatum].map(([datum, v]) => ({\n        datum,\n        lagen: v.entries.length,\n        actueel: actueel(datum),\n      })),\n      onderweg: [...jobs.current.values()].filter((j) => j.epoch === run).map((j) => j.datum),\n    }));\n  }, [\n    enabled,\n    beeld,\n    huidigeDatum,\n    sleutelNu,\n    retentionMonths,\n    referenceLevel,\n    vakRefLookup,\n    datums,\n    tik,\n    vooruitladen,\n    metadataVooruitladen,\n    beweegt,\n    perDatum,\n    revision,\n    montage,\n    preparedTiles,\n    bronContext,\n    pakketDatums,\n  ]);\n\n  return useMemo(() => {\n    const isActueel = (datum: string) => {\n      const v = perDatum.get(datum);\n      return !!beeld && !!v && dekt(v, referenceLevel, retentionMonths, beeld);\n    };\n    if (huidigeDatum && isActueel(huidigeDatum)) actieveDatumRef.current = huidigeDatum;\n    const actieveDatum = actieveDatumRef.current;\n    const venster = huidigeDatum ? maandVenster(datums, huidigeDatum) : [];\n    const toegelaten = new Set([\n      ...(preparedTiles || montage.context === sleutelNu ? montage.datums : []),\n      ...pakketDatums,\n    ]);\n    // Warm sources survive an arbitrarily distant date step. Metadata alone\n    // does not admit all dates at once after a viewport change.\n    const gemonteerd = new Set([\n      actieveDatum,\n      ...(preparedTiles ? [...toegelaten] : !beweegt ? [...toegelaten].filter(isActueel) : []),\n    ]);\n    const unie = new Map<string, MaandLaagEntry>();\n    const datumSleutels: Record<string, string[]> = {};\n    const actieveSleutels = new Set<string>();\n    const bewaard = montage.bronContext === bronContext ? montage.bewaard : [];\n    for (const datum of new Set([...gemonteerd, ...bewaard])) {\n      if (!datum || !enabled) continue;\n      const v = perDatum.get(datum);\n      if (!v || v.refLevel !== referenceLevel || v.retention !== retentionMonths) continue;\n      // Zooming into a previously broad solution must also shrink its sources.\n      const entries = datasetsInBeeld(v.entries, beeld).filter(\n        (entry) => gemonteerd.has(datum) || isPakket(entry)\n      );\n      datumSleutels[datum] = entries.map((entry) => laagSleutel(entry.id, entry.refLevel));\n      for (const entry of entries) {\n        const key = laagSleutel(entry.id, entry.refLevel);\n        unie.set(key, entry);\n        if (datum === actieveDatum) actieveSleutels.add(key);\n      }\n    }\n    const klareDatums = datums.filter((d) => enabled && gemonteerd.has(d) && isActueel(d));\n    const ladendeDatums = [...jobs.current.values()]\n      .filter((j) => j.epoch === epoch.current)\n      .map((j) => j.datum);\n    const failure = huidigeDatum ? failures.current.get(huidigeDatum) : undefined;\n    return {\n      lagen: [...unie.values()],\n      actieveSleutels,\n      klareDatums,\n      datumSleutels,\n      ladendeDatums,\n      bezigMetKlaarzetten:\n        enabled &&\n        venster\n          .slice(1)\n          .some(\n            (d) =>\n              (!isActueel(d) || !gemonteerd.has(d)) &&\n              (failures.current.get(d)?.attempts ?? 0) < MAX_POGINGEN\n          ),\n      fout: failure ? 'Peiling kon niet worden geladen.' : null,\n    };\n  }, [\n    perDatum,\n    huidigeDatum,\n    beeld,\n    referenceLevel,\n    retentionMonths,\n    datums,\n    enabled,\n    beweegt,\n    revision,\n    montage,\n    preparedTiles,\n    bronContext,\n    pakketDatums,\n    isPakket,\n    sleutelNu,\n  ]);\n}\n","import { useEffect, useState } from 'react';\n\nimport { apiAisTracksRetrieve } from '@/js/api';\nimport { datumsOpAfstand } from '@/js/lib/datum-afstand';\nimport { transformGeoJSON } from '@/js/lib/projections';\n\n/** Terugvalvenster wanneer er geen vorige peiling is. */\nconst TERUGVAL_DAGEN = 30;\n\n/**\n * Het tijdvenster dat bij een peildatum hoort: van de vorige peiling tot deze.\n * Zo verklaren de tracks precies de bodemverandering die op dat moment op de\n * kaart staat — gebaggerd híer, gestort dáár, en dit is het resultaat.\n */\nexport function vensterVoorPeiling(\n  datums: string[],\n  geselecteerd: string | null\n): { van: string; tot: string } | null {\n  if (!geselecteerd) return null;\n  const index = datums.indexOf(geselecteerd);\n  if (index < 0) return null;\n  if (index > 0) return { van: datums[index - 1], tot: geselecteerd };\n\n  const terug = new Date(`${geselecteerd}T00:00:00Z`);\n  terug.setUTCDate(terug.getUTCDate() - TERUGVAL_DAGEN);\n  return { van: terug.toISOString().slice(0, 10), tot: geselecteerd };\n}\n\n/** De periode die de AIS-aanlevering beslaat, zoals het endpoint hem meegeeft. */\nexport interface Dekking {\n  van: string;\n  tot: string;\n}\n\nfunction datum(iso: string): string {\n  return new Date(iso).toLocaleDateString('nl-NL', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric',\n  });\n}\n\n/**\n * Waarom er niets te zien is, in woorden.\n *\n * Een lege laag heeft twee heel verschillende oorzaken die er op de kaart\n * identiek uitzien: er is niet gevaren, of de AIS-aanlevering reikt hier niet\n * tot. Dat tweede is bij de eerste export het normale geval — de tijdlijn\n * begint in januari 2025 en de export in maart 2026, dus op driekwart van de\n * rail is er niets — en zonder uitleg leest dat als een kapotte laag.\n *\n * Losse functie zodat de regels te testen zijn zonder een fetch na te bootsen.\n */\nexport function dekkingsmelding(\n  venster: { van: string; tot: string } | null,\n  dekking: Dekking | null,\n  aantalFeatures: number\n): string | null {\n  if (!venster) return null;\n  if (!dekking) return 'Er zijn nog geen AIS-gegevens aangeleverd.';\n\n  const vanaf = dekking.van.slice(0, 10);\n  const totEnMet = dekking.tot.slice(0, 10);\n  const periode = `De aanlevering loopt van ${datum(dekking.van)} tot en met ${datum(dekking.tot)}.`;\n\n  if (venster.tot <= vanaf || venster.van >= totEnMet) {\n    return `Geen AIS-gegevens voor deze periode. ${periode}`;\n  }\n  if (venster.van < vanaf || venster.tot > totEnMet) {\n    return `Een deel van deze periode valt buiten de AIS-gegevens. ${periode}`;\n  }\n  return aantalFeatures === 0 ? 'Geen vaarbewegingen in deze periode.' : null;\n}\n\n/** Eén opgehaald venster, klaar om te tekenen. */\ninterface Venstergegevens {\n  geojson: GeoJSON.FeatureCollection | null;\n  dekking: Dekking | null;\n  aantal: number;\n}\n\n/**\n * Hoeveel vensters we vasthouden. Een maandvenster is zo'n 40.000 punten; als\n * omgerekende GeoJSON kost dat een paar megabyte, dus dit is geen gratis\n * getal. Zes dekt heen en weer stappen rond een peiling ruim.\n */\nconst CACHE_MAX = 6;\n\n/** Hoeveel buurvensters we alvast halen zodra het huidige er staat. */\nconst VOORUIT = 2;\n\n/**\n * De vensters die al opgehaald zijn, en de vensters die onderweg zijn.\n *\n * Buiten de hook, want twee dingen moeten hem overleven: het uitzetten van de\n * laag (aanzetten hoort daarna gratis te zijn) en het opnieuw aankoppelen van\n * de kaart. De tegellagen doen hetzelfde met hun voorbereide maanden.\n *\n * `lopend` staat er los naast zodat een venster dat vooruitgeladen wordt en\n * intussen aangeklikt, één request blijft in plaats van twee.\n */\nconst cache = new Map<string, Venstergegevens>();\nconst lopend = new Map<string, Promise<Venstergegevens>>();\n\nfunction sleutel(van: string, tot: string): string {\n  return `${van}|${tot}`;\n}\n\nfunction onthoud(k: string, gegevens: Venstergegevens): void {\n  // Opnieuw invoegen zet de sleutel achteraan: wat het langst niet gevraagd\n  // is valt er als eerste uit.\n  cache.delete(k);\n  cache.set(k, gegevens);\n  while (cache.size > CACHE_MAX) {\n    const oudste = cache.keys().next().value;\n    if (oudste === undefined) break;\n    cache.delete(oudste);\n  }\n}\n\n/** Haal een venster op, of geef het lopende of bewaarde antwoord terug. */\nfunction haalVenster(van: string, tot: string): Promise<Venstergegevens> {\n  const k = sleutel(van, tot);\n  const bewaard = cache.get(k);\n  if (bewaard) return Promise.resolve(bewaard);\n  const onderweg = lopend.get(k);\n  if (onderweg) return onderweg;\n\n  const belofte = apiAisTracksRetrieve({ query: { van, tot } })\n    .then(({ data }) => {\n      const fc = data as unknown as\n        | (GeoJSON.FeatureCollection & { dekking?: Dekking | null })\n        | undefined;\n      const aantal = fc?.features?.length ?? 0;\n      const gegevens: Venstergegevens = {\n        // Omrekenen naar WGS84 gebeurt hier en niet bij het tekenen, zodat een\n        // venster uit de cache geen 40.000 punten opnieuw hoeft te projecteren.\n        geojson: aantal ? transformGeoJSON(fc as GeoJSON.FeatureCollection) : null,\n        dekking: fc?.dekking ?? null,\n        aantal,\n      };\n      onthoud(k, gegevens);\n      return gegevens;\n    })\n    .finally(() => {\n      lopend.delete(k);\n    });\n\n  lopend.set(k, belofte);\n  return belofte;\n}\n\n/** Alleen voor tests: begin met een schone cache. */\nexport function vergeetVensters(): void {\n  cache.clear();\n  lopend.clear();\n}\n\n/**\n * Vaartracks voor het venster dat bij de geselecteerde peiling hoort.\n *\n * `ingeschakeld` is geen luxe: de laag staat standaard uit omdat ze een\n * request per datumstap kost, en met de tijdslider op afspelen is dat elke\n * 2,5 seconde een venster van een maand — seconden CPU en megabytes GeoJSON\n * per stap. Zonder deze vlag betaalt iedereen die prijs, ook wie de laag nooit\n * aanzet. Zelfde patroon als de `enabled` van `useAtpDatasets`.\n *\n * Een venster kost op productie twee tot drie seconden serverwerk. Daarom\n * dezelfde twee zetten als bij de maandlagen: bewaren wat al gehaald is, en de\n * buurvensters alvast halen zodra het huidige er staat — in de volgorde van\n * `datumsOpAfstand`, dezelfde die de tijdlijnbalk aankondigt. Een stap terug of\n * vooruit is dan een wissel en geen wachttijd.\n */\nexport function useScheepsactiviteit(\n  datums: string[],\n  geselecteerd: string | null,\n  ingeschakeld = true\n) {\n  const [geojson, setGeojson] = useState<GeoJSON.FeatureCollection | null>(null);\n  const [laden, setLaden] = useState(false);\n  const [fout, setFout] = useState<string | null>(null);\n  const [melding, setMelding] = useState<string | null>(null);\n\n  const venster = vensterVoorPeiling(datums, geselecteerd);\n  const van = venster?.van ?? null;\n  const tot = venster?.tot ?? null;\n\n  useEffect(() => {\n    // Bij uitschakelen ook opruimen: anders blijft de foutmelding van de\n    // vorige poging in het laagpaneel staan over een laag die niet meer aan\n    // staat, en houdt de oude FeatureCollection geheugen bezet. De cache zelf\n    // blijft wel staan — opnieuw aanzetten hoort gratis te zijn.\n    if (!ingeschakeld || !van || !tot) {\n      setGeojson(null);\n      setLaden(false);\n      setFout(null);\n      setMelding(null);\n      return;\n    }\n\n    const toon = (gegevens: Venstergegevens) => {\n      setGeojson(gegevens.geojson);\n      setMelding(dekkingsmelding({ van, tot }, gegevens.dekking, gegevens.aantal));\n    };\n\n    const bewaard = cache.get(sleutel(van, tot));\n    if (bewaard) {\n      // Geen laadstand en geen request: dit venster staat al klaar.\n      setFout(null);\n      setLaden(false);\n      toon(bewaard);\n      return;\n    }\n\n    let afgebroken = false;\n    setLaden(true);\n    // Fout en leeg zijn niet hetzelfde: een mislukte fetch is geen maand\n    // zonder vaarbewegingen. Beide toestanden blijven apart zichtbaar.\n    setFout(null);\n\n    // De vorige tracks blijven intussen staan. Wegpoetsen en pas seconden\n    // later opnieuw tekenen leest als een laag die knippert.\n    haalVenster(van, tot)\n      .then((gegevens) => {\n        if (!afgebroken) toon(gegevens);\n      })\n      .catch(() => {\n        if (!afgebroken) {\n          setGeojson(null);\n          setMelding(null);\n          setFout('Scheepsactiviteit kon niet worden geladen.');\n        }\n      })\n      .finally(() => {\n        if (!afgebroken) setLaden(false);\n      });\n\n    return () => {\n      afgebroken = true;\n    };\n  }, [van, tot, ingeschakeld]);\n\n  useEffect(() => {\n    // Pas vooruitladen als het venster dat de gebruiker nú bekijkt binnen is:\n    // die request hoort niet achter twee andere in de rij te staan.\n    if (!ingeschakeld || !geselecteerd || laden) return;\n    let gestopt = false;\n\n    void (async () => {\n      for (const buur of datumsOpAfstand(datums, geselecteerd).slice(0, VOORUIT)) {\n        if (gestopt) return;\n        const buurvenster = vensterVoorPeiling(datums, buur);\n        if (!buurvenster) continue;\n        if (cache.has(sleutel(buurvenster.van, buurvenster.tot))) continue;\n        try {\n          // Eén tegelijk. De server rekent elk venster helemaal door, dus drie\n          // tegelijk maakt alle drie langzamer in plaats van één sneller.\n          await haalVenster(buurvenster.van, buurvenster.tot);\n        } catch {\n          // Een buurvenster dat niet lukt is geen melding waard: de gebruiker\n          // kijkt er niet naar. Stapt hij er wél heen, dan probeert het effect\n          // hierboven het opnieuw en meldt het dan alsnog.\n        }\n      }\n    })();\n\n    return () => {\n      gestopt = true;\n    };\n  }, [datums, geselecteerd, ingeschakeld, laden]);\n\n  return { geojson, laden, fout, melding };\n}\n","/**\n * Per-tile baggervak masking for COG raster clipping.\n *\n * Rasterises baggervak polygons onto 256×256 tile canvases so that\n * the COG protocol wrapper can clip raster data to baggervak boundaries.\n * Each pixel stores a baggervak index (0 = outside, 1..N = feature index)\n * for future per-pixel reference-level colouring.\n */\n\nconst TILE_SIZE = 256;\n\n// ---------------------------------------------------------------------------\n// Tile math (Web Mercator ↔ WGS84)\n// ---------------------------------------------------------------------------\n\ntype Bounds = [west: number, south: number, east: number, north: number];\n\nexport function tileBounds(z: number, x: number, y: number): Bounds {\n  const n = 2 ** z;\n  const west = (x / n) * 360 - 180;\n  const east = ((x + 1) / n) * 360 - 180;\n  const north = (Math.atan(Math.sinh(Math.PI * (1 - (2 * y) / n))) * 180) / Math.PI;\n  const south = (Math.atan(Math.sinh(Math.PI * (1 - (2 * (y + 1)) / n))) * 180) / Math.PI;\n  return [west, south, east, north];\n}\n\n/** Mercator Y projection (lat in degrees → unitless) */\nfunction mercY(lat: number): number {\n  return Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));\n}\n\n/** Convert a WGS84 coordinate to pixel position within a tile. */\nfunction lngLatToTilePixel(\n  lng: number,\n  lat: number,\n  bounds: Bounds,\n  size: number,\n): [number, number] {\n  const [west, south, east, north] = bounds;\n  const px = ((lng - west) / (east - west)) * size;\n  const mNorth = mercY(north);\n  const mSouth = mercY(south);\n  const py = ((mNorth - mercY(lat)) / (mNorth - mSouth)) * size;\n  return [px, py];\n}\n\n// ---------------------------------------------------------------------------\n// Bounding-box helpers\n// ---------------------------------------------------------------------------\n\ntype FeatureBbox = [minLng: number, minLat: number, maxLng: number, maxLat: number];\n\nfunction computeFeatureBbox(feature: GeoJSON.Feature): FeatureBbox {\n  let minLng = Infinity,\n    minLat = Infinity,\n    maxLng = -Infinity,\n    maxLat = -Infinity;\n\n  const visit = (coords: number[]) => {\n    if (coords[0] < minLng) minLng = coords[0];\n    if (coords[1] < minLat) minLat = coords[1];\n    if (coords[0] > maxLng) maxLng = coords[0];\n    if (coords[1] > maxLat) maxLat = coords[1];\n  };\n\n  const geom = feature.geometry;\n  if (geom.type === 'Polygon') {\n    for (const ring of geom.coordinates) for (const c of ring) visit(c);\n  } else if (geom.type === 'MultiPolygon') {\n    for (const poly of geom.coordinates) for (const ring of poly) for (const c of ring) visit(c);\n  }\n\n  return [minLng, minLat, maxLng, maxLat];\n}\n\nfunction bboxOverlaps(a: Bounds | FeatureBbox, b: Bounds | FeatureBbox): boolean {\n  return a[0] <= b[2] && a[2] >= b[0] && a[1] <= b[3] && a[3] >= b[1];\n}\n\n// ---------------------------------------------------------------------------\n// LRU cache\n// ---------------------------------------------------------------------------\n\nclass LRUCache<V> {\n  private map = new Map<string, V>();\n  constructor(private maxSize: number) {}\n\n  get(key: string): V | undefined {\n    const v = this.map.get(key);\n    if (v !== undefined) {\n      // Move to end (most recently used)\n      this.map.delete(key);\n      this.map.set(key, v);\n    }\n    return v;\n  }\n\n  set(key: string, value: V) {\n    if (this.map.has(key)) this.map.delete(key);\n    this.map.set(key, value);\n    if (this.map.size > this.maxSize) {\n      // Delete oldest entry\n      const first = this.map.keys().next().value;\n      if (first !== undefined) this.map.delete(first);\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// BaggervakMask\n// ---------------------------------------------------------------------------\n\ninterface CachedTile {\n  mask: Uint8Array;\n  clipCanvas: OffscreenCanvas;\n}\n\nexport class BaggervakMask {\n  private features: GeoJSON.Feature[];\n  private featureBboxes: FeatureBbox[];\n  private cache = new LRUCache<CachedTile>(200);\n\n  constructor(...collections: GeoJSON.FeatureCollection[]) {\n    this.features = collections.flatMap((c) => c.features);\n    this.featureBboxes = this.features.map(computeFeatureBbox);\n  }\n\n  /** Per-pixel baggervak index: 0 = outside, 1..N = 1-based feature index. */\n  getMask(z: number, x: number, y: number): Uint8Array {\n    return this.getTile(z, x, y).mask;\n  }\n\n  /** Canvas with filled baggervak polygons (for destination-in compositing). */\n  getClipCanvas(z: number, x: number, y: number): OffscreenCanvas {\n    return this.getTile(z, x, y).clipCanvas;\n  }\n\n  /** Get the GeoJSON feature for a 1-based baggervak index. */\n  getFeature(index: number): GeoJSON.Feature | null {\n    return index >= 1 && index <= this.features.length ? this.features[index - 1] : null;\n  }\n\n  private getTile(z: number, x: number, y: number): CachedTile {\n    const key = `${z}/${x}/${y}`;\n    const cached = this.cache.get(key);\n    if (cached) return cached;\n\n    const tile = this.rasterize(z, x, y);\n    this.cache.set(key, tile);\n    return tile;\n  }\n\n  private rasterize(z: number, x: number, y: number): CachedTile {\n    const bounds = tileBounds(z, x, y);\n\n    // Quick reject: if no feature bbox overlaps this tile, return empty\n    const anyOverlap = this.featureBboxes.some((fb) => bboxOverlaps(bounds, fb));\n    if (!anyOverlap) {\n      const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);\n      return {\n        mask: new Uint8Array(TILE_SIZE * TILE_SIZE),\n        clipCanvas: canvas,\n      };\n    }\n\n    const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);\n    const ctx = canvas.getContext('2d')!;\n\n    for (let fi = 0; fi < this.features.length; fi++) {\n      if (!bboxOverlaps(bounds, this.featureBboxes[fi])) continue;\n\n      const feature = this.features[fi];\n      const geom = feature.geometry;\n      const index = fi + 1; // 1-based\n      // Encode index in R channel (max 255 features with single-channel encoding).\n      // Use full alpha so the clip canvas has opaque pixels inside baggervakken.\n      ctx.fillStyle = `rgb(${index}, 0, 0)`;\n\n      if (geom.type === 'Polygon') {\n        this.drawPolygon(ctx, geom.coordinates, bounds);\n      } else if (geom.type === 'MultiPolygon') {\n        for (const poly of geom.coordinates) {\n          this.drawPolygon(ctx, poly, bounds);\n        }\n      }\n    }\n\n    // Read back the mask (R channel = baggervak index).\n    // NOTE: Canvas anti-aliasing can blend R values at polygon boundaries,\n    // so the index may be inaccurate for edge pixels. This is fine for clip\n    // (which uses the clipCanvas directly) but should be kept in mind if\n    // getMask() is later used for per-pixel reference-level colouring.\n    const imageData = ctx.getImageData(0, 0, TILE_SIZE, TILE_SIZE);\n    const mask = new Uint8Array(TILE_SIZE * TILE_SIZE);\n    for (let i = 0; i < mask.length; i++) {\n      const r = imageData.data[i * 4];\n      const a = imageData.data[i * 4 + 3];\n      mask[i] = a > 128 ? r : 0;\n    }\n\n    return { mask, clipCanvas: canvas };\n  }\n\n  private drawPolygon(\n    ctx: OffscreenCanvasRenderingContext2D,\n    rings: number[][][],\n    bounds: Bounds,\n  ) {\n    ctx.beginPath();\n    for (const ring of rings) {\n      for (let i = 0; i < ring.length; i++) {\n        const [px, py] = lngLatToTilePixel(ring[i][0], ring[i][1], bounds, TILE_SIZE);\n        if (i === 0) ctx.moveTo(px, py);\n        else ctx.lineTo(px, py);\n      }\n      ctx.closePath();\n    }\n    ctx.fill();\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Module-level state (same pattern as setColorFunction in maplibre-cog-protocol)\n// ---------------------------------------------------------------------------\n\nlet clipMask: BaggervakMask | null = null;\n\nexport function setClipMask(mask: BaggervakMask | null): void {\n  clipMask = mask;\n}\n\nexport function getClipMask(): BaggervakMask | null {\n  return clipMask;\n}\n\n// ---------------------------------------------------------------------------\n// Tile compositing\n// ---------------------------------------------------------------------------\n\n/**\n * Apply the baggervak clip mask to a tile ImageBitmap.\n * Uses `destination-in` compositing: only pixels where both the tile\n * AND the mask are opaque remain visible.\n */\nexport async function applyClipToTile(\n  bitmap: ImageBitmap,\n  mask: BaggervakMask,\n  z: number,\n  x: number,\n  y: number,\n): Promise<ImageBitmap> {\n  const clipCanvas = mask.getClipCanvas(z, x, y);\n\n  const canvas = new OffscreenCanvas(TILE_SIZE, TILE_SIZE);\n  const ctx = canvas.getContext('2d')!;\n\n  // Draw original tile\n  ctx.drawImage(bitmap, 0, 0);\n\n  // Composite: keep only pixels where the mask is opaque\n  ctx.globalCompositeOperation = 'destination-in';\n  ctx.drawImage(clipCanvas, 0, 0);\n\n  return createImageBitmap(canvas);\n}\n","/**\n * De bron-URL van een COG-laag op de kaart.\n *\n * MapLibre cachet tegels per bron-URL. Alles wat de kleur van een pixel bepaalt\n * moet daarom in het hekje staan, anders blijft na een wissel de oude tegel\n * liggen en lijkt de knop stuk. Het `cog://`-protocol knipt de hash er weer af\n * voordat het gaat ophalen, dus de SAS-URL erachter blijft ongeschonden.\n *\n * Stond eerst twee keer los in `MapView` — één keer voor de reguliere peilingen\n * en één keer voor de aantoonpeilingen, met net een ander voorvoegsel. Dat is\n * precies het soort regel dat op één plek bijgewerkt wordt en op de andere\n * niet.\n */\n\nimport { heeftColormapKeuze } from './depth-colors';\nimport { kaartExperiment } from './kaart-experiment';\n\nimport type { Colormap, ReferenceLevel } from './depth-colors';\n\nexport interface CogSourceOpties {\n  /** Authenticated, versioned PNG package, selected from dataset metadata. */\n  tileUrl?: string;\n  refLevel: ReferenceLevel;\n  colormap: Colormap;\n  /** Of de bathymetrie op de baggervakken geknipt wordt. */\n  clip: boolean;\n  /** Aantoonpeiling in plaats van een reguliere peiling (#113). */\n  atp: boolean;\n}\n\nexport function cogSourceUrl(sasUrl: string, opties: CogSourceOpties): string {\n  const { refLevel, colormap, clip, atp } = opties;\n  if (opties.tileUrl && !clip) return `pngtiles://${opties.tileUrl}`;\n  if (\n    kaartExperiment('kaartPreparedTiles', [1], 0) &&\n    refLevel === 'ngd' &&\n    colormap === 'discreet' &&\n    !clip &&\n    !atp\n  ) {\n    const url = new URL(sasUrl);\n    const match = url.pathname.match(/^\\/display\\/mm1-v1\\/(\\d+)\\.tif$/);\n    if (url.origin === window.location.origin && match)\n      return `${url.origin}/prepared-c5/ngd-discreet/${match[1]}.json`;\n  }\n\n  // Alleen meenemen als de keuze werkelijk iets met de pixels doet. Op een peil\n  // is de kleurschaal niet te kiezen; zou hij daar toch in de sleutel staan,\n  // dan gooit een wissel het hele tegelbeeld weg zonder dat er iets verandert.\n  const schaal = heeftColormapKeuze(refLevel) ? `_${colormap}` : '';\n\n  return `cog://${sasUrl}#${atp ? '_atp' : '_v'}=${refLevel}${schaal}${clip ? '_clip' : ''}`;\n}\n","/**\n * Union-bbox over één of meer GeoJSON FeatureCollections.\n *\n * Gebruikt voor de init-view van de kaart: in plaats van een hardcoded\n * centrum/zoom fitten we op alle geladen shapes (baggervakken + overlays).\n * Coordinaten mogen een Z-component hebben (PolygonZ) — die wordt genegeerd.\n */\n\nexport type LngLatBounds = [[number, number], [number, number]];\n\nfunction walkCoordinates(\n  coords: unknown,\n  visit: (lng: number, lat: number) => void\n): void {\n  if (!Array.isArray(coords)) return;\n  if (typeof coords[0] === 'number' && typeof coords[1] === 'number') {\n    visit(coords[0], coords[1]);\n    return;\n  }\n  for (const child of coords) {\n    walkCoordinates(child, visit);\n  }\n}\n\nexport function geojsonBounds(\n  ...collections: (GeoJSON.FeatureCollection | null | undefined)[]\n): LngLatBounds | null {\n  let minLng = Infinity;\n  let minLat = Infinity;\n  let maxLng = -Infinity;\n  let maxLat = -Infinity;\n\n  for (const collection of collections) {\n    for (const feature of collection?.features ?? []) {\n      const geometry = feature.geometry as (GeoJSON.Geometry & { coordinates?: unknown }) | null;\n      walkCoordinates(geometry?.coordinates, (lng, lat) => {\n        if (lng < minLng) minLng = lng;\n        if (lat < minLat) minLat = lat;\n        if (lng > maxLng) maxLng = lng;\n        if (lat > maxLat) maxLat = lat;\n      });\n    }\n  }\n\n  if (!Number.isFinite(minLng) || !Number.isFinite(minLat)) return null;\n  return [\n    [minLng, minLat],\n    [maxLng, maxLat],\n  ];\n}\n","/**\n * De kleuren van de vectorlagen op de kaart.\n *\n * Eén plek, omdat het werkblad naast elke laag een symbool zet dat er precies\n * zo uit moet zien als de laag zelf: een legenda die een andere kleur belooft\n * dan de kaart laat zien is erger dan geen legenda.\n */\nexport const LAAG_KLEUR = {\n  baggervakken: '#1e3a5f',\n  vaargeul: '#0891b2',\n  randstroken: '#d97706',\n  passeerstroken: '#2563eb',\n  taluds: '#7c3aed',\n  stortlocaties: '#ea580c',\n} as const;\n","/**\n * Reliëf: een schaduwlaag over de dieptekleuren, uit dezelfde COG (WP6).\n *\n * De dieptekleuren laten zien hoe diep het is, niet hoe de bodem eruitziet.\n * Duinen van twintig centimeter tot een meter verdwijnen in één kleurklasse.\n * Een schaduw zoals op een reliëfkaart maakt ze zichtbaar, en omdat de laag de\n * maandlagen volgt, lopen ze mee als je de tijdslider afspeelt. Dat is het\n * hele doel: laten zien hoe de bodem beweegt. Het is geen voorspelling.\n *\n * Berekend in de browser, per tegel, uit de COG die al geladen wordt: geen\n * backendwerk en geen nieuwe tegelvarianten. Een tegel wordt gelezen met een\n * rand van één pixel, zodat de helling ook aan de tegelrand klopt.\n *\n * De laag is grijs met transparantie, zwart voor schaduw en wit voor licht.\n * Zo werkt hij over elke kleurschaal, ook de kanskaart.\n */\n\nimport { leesVenster } from './bathycog-delta';\nimport { tel } from './tijdlijn-meting';\nimport { tileBboxMercator } from './tile-geometrie';\n\nconst TEGEL = 256;\n\n/**\n * Verticale overdrijving. Een duin van dertig centimeter over vijf meter is\n * een helling van zes procent; zonder overdrijving geeft dat nauwelijks\n * schaduw. Acht keer maakt duinen zichtbaar zonder dat de taluds (1 op 5)\n * alles overstemmen.\n */\nexport const OVERDRIJVING = 8;\nconst ZON_AZIMUT_GRADEN = 315;\nconst ZON_HOOGTE_GRADEN = 45;\n/** Hoe zwaar schaduw en licht doorkomen; licht bewust zachter dan schaduw. */\nconst SCHADUW_STERKTE = 0.85;\nconst LICHT_STERKTE = 0.5;\n\nconst ZENIT = ((90 - ZON_HOOGTE_GRADEN) * Math.PI) / 180;\nconst AZIMUT = (((360 - ZON_AZIMUT_GRADEN + 90) % 360) * Math.PI) / 180;\nconst VLAK = Math.cos(ZENIT);\n\n/**\n * Schaduw volgens Horn (1981), dezelfde formule als `gdaldem hillshade`.\n *\n * @param z dieptes in meter, rij voor rij, `(breedte + 2) × (hoogte + 2)`: de\n *   tegel met een rand van één pixel. NaN is geen data.\n * @param celMeter pixelgrootte op de grond in meter.\n * @returns RGBA voor `breedte × hoogte` pixels.\n */\nexport function schaduw(\n  z: ArrayLike<number>,\n  breedte: number,\n  hoogte: number,\n  celMeter: number,\n  overdrijving = OVERDRIJVING\n): Uint8ClampedArray<ArrayBuffer> {\n  const rgba = new Uint8ClampedArray(breedte * hoogte * 4);\n  const rij = breedte + 2;\n  for (let r = 0; r < hoogte; r++) {\n    for (let k = 0; k < breedte; k++) {\n      const midden = (r + 1) * rij + (k + 1);\n      const a = z[midden - rij - 1];\n      const b = z[midden - rij];\n      const c = z[midden - rij + 1];\n      const d = z[midden - 1];\n      const f = z[midden + 1];\n      const g = z[midden + rij - 1];\n      const h = z[midden + rij];\n      const i = z[midden + rij + 1];\n      const uit = (r * breedte + k) * 4;\n      if (!Number.isFinite(a + b + c + d + f + g + h + i) || !Number.isFinite(z[midden])) {\n        continue; // transparant\n      }\n      const dzdx = (c + 2 * f + i - (a + 2 * d + g)) / (8 * celMeter);\n      const dzdy = (g + 2 * h + i - (a + 2 * b + c)) / (8 * celMeter);\n      const helling = Math.atan(overdrijving * Math.hypot(dzdx, dzdy));\n      const richting = Math.atan2(dzdy, -dzdx);\n      const licht =\n        Math.cos(ZENIT) * Math.cos(helling) +\n        Math.sin(ZENIT) * Math.sin(helling) * Math.cos(AZIMUT - richting);\n      const verschil = licht - VLAK;\n      if (verschil < 0) {\n        rgba[uit + 3] = Math.min(255, (-verschil / VLAK) * 255 * SCHADUW_STERKTE);\n      } else {\n        rgba[uit] = 255;\n        rgba[uit + 1] = 255;\n        rgba[uit + 2] = 255;\n        rgba[uit + 3] = Math.min(255, (verschil / (1 - VLAK)) * 255 * LICHT_STERKTE);\n      }\n    }\n  }\n  return rgba;\n}\n\n/** Grondmaat van een pixel in een Web-Mercatortegel: de projectie rekt uit met 1 / cos(breedte). */\nexport function grondmaatPixel(z: number, y: number): number {\n  const [minX, minY, maxX, maxY] = tileBboxMercator(z, 0, y);\n  const middenY = (minY + maxY) / 2;\n  const breedtegraad = 2 * Math.atan(Math.exp(middenY / 6378137)) - Math.PI / 2;\n  return ((maxX - minX) / TEGEL) * Math.cos(breedtegraad);\n}\n\n/**\n * Een kleine cache van gerenderde tegels. Reliëf staat standaard uit en\n * hoort niet het geheugen van de dieptetegels op te eten; zestig tegels is\n * ongeveer vijftien megabyte, genoeg om heen en weer te stappen in beeld.\n */\nconst CACHE_MAX = 60;\nconst cache = new Map<string, ImageData>();\nconst lopend = new Map<string, Promise<ImageData>>();\n\n/** Test-only. */\nexport function __leegReliefCache(): void {\n  cache.clear();\n  lopend.clear();\n}\n\nasync function render(url: string, z: number, x: number, y: number): Promise<ImageData> {\n  const [minX, minY, maxX, maxY] = tileBboxMercator(z, x, y);\n  const pixel = (maxX - minX) / TEGEL;\n  const dieptes = await leesVenster(\n    url,\n    [minX - pixel, minY - pixel, maxX + pixel, maxY + pixel],\n    TEGEL + 2,\n    TEGEL + 2\n  );\n  return new ImageData(schaduw(dieptes, TEGEL, TEGEL, grondmaatPixel(z, y)), TEGEL, TEGEL);\n}\n\nexport async function renderReliefTile(\n  url: string,\n  z: number,\n  x: number,\n  y: number\n): Promise<ImageBitmap> {\n  const sleutel = `${url}|${z}/${x}/${y}`;\n  let beeld = cache.get(sleutel);\n  if (beeld) {\n    tel('reliëf: treffer');\n    cache.delete(sleutel);\n    cache.set(sleutel, beeld);\n  } else {\n    let bezig = lopend.get(sleutel);\n    if (!bezig) {\n      tel('reliëf: render');\n      bezig = render(url, z, x, y);\n      lopend.set(sleutel, bezig);\n    }\n    try {\n      beeld = await bezig;\n    } finally {\n      lopend.delete(sleutel);\n    }\n    while (cache.size >= CACHE_MAX) {\n      const oudste = cache.keys().next().value;\n      if (oudste === undefined) break;\n      cache.delete(oudste);\n    }\n    cache.set(sleutel, beeld);\n  }\n  return createImageBitmap(beeld);\n}\n","/**\n * Reisfase van een scheepstrack (#WP5). Wat het schip deed tijdens het\n * venster tussen de vorige en de geselecteerde peiling.\n *\n * `onbekend` is geen vierde bezigheid maar een gat in de boekhouding: de\n * AIS-export loopt verder door dan de reizendatabase, dus een positie kan\n * bestaan zonder dat er al een reis (met fase) aan gekoppeld is. Dat blijft\n * zo bij elke maandaanlevering, en verdient daarom een eigen uitleg in de\n * legenda in plaats van stil mee te lopen met een van de drie echte fases.\n */\nexport type Fase = 'baggeren' | 'lossen' | 'varen' | 'onbekend';\n\n/** Volgorde waarin de fases in de legenda getoond worden. */\nexport const FASE_VOLGORDE: Fase[] = ['baggeren', 'lossen', 'varen', 'onbekend'];\n\n/**\n * Kleur per fase. Gedeeld tussen de kaartlaag (`MapView`) en de legenda\n * (`WerkbladLagen`), zodat de twee niet uit elkaar kunnen lopen.\n */\nexport const FASE_KLEUR: Record<Fase, string> = {\n  baggeren: '#c2410c',\n  lossen: '#f59e0b',\n  varen: '#475467',\n  onbekend: '#9ca3af',\n};\n\n/**\n * Dekking per fase, tussen 0 (onzichtbaar) en 1 (vol).\n *\n * De tracks zijn context bij de bodemverandering, niet het onderwerp van de\n * kaart: ze lopen dwars over de dieptekleuren heen en zijn met vier fases al\n * snel een web van lijnen. Daarom tekenen we ze allemaal wat ingehouden, en\n * de fases naar hun zeggingskracht.\n *\n * `baggeren` en `lossen` zijn wat je wilt zien — dáár is de bodem veranderd.\n * `varen` is het verbindingsweefsel ertussen: het maakt de route leesbaar maar\n * zegt niets over het werk, en beslaat verreweg de meeste kilometers. Datzelfde\n * geldt voor `onbekend`, dat bovendien hele maanden kan vullen zolang de\n * reizendatabase achterloopt op de AIS-export; vol getekend zou juist het\n * minst informatieve deel van de kaart het meest opvallen.\n *\n * Gedeeld tussen de kaartlaag en de legenda, net als `FASE_KLEUR`, zodat een\n * legendastreepje dezelfde tint heeft als de lijn op de kaart.\n */\nexport const FASE_DEKKING: Record<Fase, number> = {\n  baggeren: 0.7,\n  lossen: 0.7,\n  varen: 0.3,\n  onbekend: 0.25,\n};\n\n/** Legendatekst per fase. */\nexport const FASE_LABELS: Record<Fase, string> = {\n  baggeren: 'Baggeren',\n  lossen: 'Lossen',\n  varen: 'Varen',\n  onbekend: 'Fase onbekend — reizen nog niet aangeleverd',\n};\n\n/**\n * Streeppatroon voor een segment dat de AIS tegenspreekt: de reisregel zegt\n * baggeren of lossen op een vak, maar het schip is daar tijdens die hele fase\n * niet in de buurt geweest. In juni 2026 gold dat voor 25 van de 275\n * baggeren- en lossensegmenten.\n *\n * Gestreept en niet weggelaten, en ook niet stilletjes omgekleurd naar varen:\n * dezelfde regels leveren de kuubs per vak, dus als de kaart de fout verbergt\n * is er niets meer dat hem laat zien. De maat is in lijnbreedtes, niet in\n * pixels.\n */\nexport const STRIJDIG_STREEP: [number, number] = [2, 2];\n\n/** Legendatekst bij het streeppatroon. */\nexport const STRIJDIG_LABEL = 'Gestreept — reisregel klopt niet met de AIS';\n","import { STATUS_LEVELS, THRESHOLDS, statusColor } from './analyse-thresholds';\nimport { rdToWgs84 } from './projections';\n\nimport type { StatusLevel } from './analyse-thresholds';\nimport type { SurveyDataset } from '@/js/api/types.gen';\n\n/**\n * De ondiepste punten per baggervak als GeoJSON (#115).\n *\n * Binnen de Eemsgeul wordt op de ondiepste waarde gestuurd, dus het getal\n * alleen is niet genoeg: de plek bepaalt waar gebaggerd moet worden. Het punt\n * komt uit dezelfde pixel als `depth_max_in_vak`, dus de marker en het getal\n * in de analysetool kunnen niet uiteenlopen.\n *\n * `shallowest_point` is `[x, y]` in EPSG:28992; MapLibre wil WGS84.\n */\n\nexport interface ShallowestPointProps {\n  code: string;\n  depth: number | null;\n  cmAboveNgd: number | null;\n  status: StatusLevel;\n  /** Kant-en-klaar label voor de kaart, bv. \"D2  -15,1 m\". */\n  label: string;\n}\n\nfunction maakLabel(code: string, depth: number | null): string {\n  if (depth == null) return code;\n  return `${code}  ${depth.toLocaleString('nl-NL', {\n    minimumFractionDigits: 1,\n    maximumFractionDigits: 1,\n  })} m`;\n}\n\nexport function shallowestPointsGeoJSON(\n  datasets: ReadonlyArray<SurveyDataset>\n): GeoJSON.FeatureCollection {\n  const features: GeoJSON.Feature[] = [];\n\n  for (const ds of datasets) {\n    const punt = ds.shallowest_point;\n    if (!punt) continue;\n\n    const [lng, lat] = rdToWgs84(punt[0], punt[1]);\n    const props: ShallowestPointProps = {\n      code: ds.baggervak_code,\n      depth: ds.depth_max_in_vak ?? null,\n      cmAboveNgd: ds.cm_above_ngd ?? null,\n      status: statusColor(ds.cm_above_ngd, THRESHOLDS.cm_above_ngd),\n      label: maakLabel(ds.baggervak_code, ds.depth_max_in_vak ?? null),\n    };\n\n    features.push({\n      type: 'Feature',\n      properties: props as unknown as GeoJSON.GeoJsonProperties,\n      geometry: { type: 'Point', coordinates: [lng, lat] },\n    });\n  }\n\n  return { type: 'FeatureCollection', features };\n}\n\n/**\n * Kleur per status voor de kaartmarker. Dezelfde indeling als de\n * statuskleuren in de analysetool, zodat kaart en tabel hetzelfde zeggen:\n * groen is onder NGD, amber is er net boven, rood is meer dan 10 cm erboven.\n */\nexport const SHALLOWEST_MARKER_COLORS: Record<StatusLevel, string> = {\n  green: '#059669',\n  amber: '#d97706',\n  red: '#e11d48',\n};\n\n/** MapLibre match-expressie op de `status`-property. */\nexport function shallowestColorExpression(): unknown[] {\n  return [\n    'match',\n    ['get', 'status'],\n    ...STATUS_LEVELS.flatMap((level) => [level, SHALLOWEST_MARKER_COLORS[level]]),\n    SHALLOWEST_MARKER_COLORS.green,\n  ];\n}\n","/**\n * Waar het label van de diepte-speld staat ten opzichte van het punt. Standaard\n * erboven; bij een klik vlak onder de bovenrand, tegen een zijkant of naast de\n * werkbalk valt het label anders (deels) buiten beeld of eronder, en is de\n * diepte niet te lezen of het kruisje niet te raken.\n */\nexport type SpeldAnker =\n  | 'bottom'\n  | 'bottom-right'\n  | 'bottom-left'\n  | 'top'\n  | 'top-right'\n  | 'top-left';\n\ninterface Rechthoek {\n  links: number;\n  rechts: number;\n  boven: number;\n  onder: number;\n}\n\n/** Label met steel en stip, ruim genomen: het label groeit met de regels erin. */\nconst LABEL_BREEDTE = 220;\nconst LABEL_HOOGTE = 130;\n/** Halve stip: de stip staat met zijn midden op het punt. */\nconst HALVE_STIP = 6;\n\n/** Volgorde van voorkeur: boven het punt eerst, dan pas eronder. */\nconst VOORKEUR: SpeldAnker[] = [\n  'bottom',\n  'bottom-right',\n  'bottom-left',\n  'top',\n  'top-right',\n  'top-left',\n];\n\n/** Verschuiving van het element zodat het midden van de stip op het punt staat. */\nexport function speldOffset(anker: SpeldAnker): [number, number] {\n  const x = anker.endsWith('-left') ? -HALVE_STIP : anker.endsWith('-right') ? HALVE_STIP : 0;\n  return [x, anker.startsWith('bottom') ? HALVE_STIP : -HALVE_STIP];\n}\n\nfunction labelVlak(x: number, y: number, anker: SpeldAnker): Rechthoek {\n  // \"-left\": de linkerkant van het element op het punt, het label loopt naar rechts.\n  const links = anker.endsWith('-left')\n    ? x - HALVE_STIP\n    : anker.endsWith('-right')\n      ? x + HALVE_STIP - LABEL_BREEDTE\n      : x - LABEL_BREEDTE / 2;\n  const boven = anker.startsWith('bottom') ? y - LABEL_HOOGTE : y;\n  return { links, rechts: links + LABEL_BREEDTE, boven, onder: boven + LABEL_HOOGTE };\n}\n\n/**\n * Kies het anker op het punt van de klik, in pixels binnen de kaart. `obstakel`\n * is wat er over de kaart ligt (de werkbalk), in dezelfde coördinaten. Past\n * niets (een heel kleine kaart), dan het gewone: boven het punt.\n */\nexport function speldAnker(\n  punt: { x: number; y: number },\n  kaart: { breedte: number; hoogte: number },\n  obstakel?: Rechthoek | null\n): SpeldAnker {\n  const past = (anker: SpeldAnker) => {\n    const vlak = labelVlak(punt.x, punt.y, anker);\n    const binnen =\n      vlak.links >= 0 &&\n      vlak.boven >= 0 &&\n      vlak.rechts <= kaart.breedte &&\n      vlak.onder <= kaart.hoogte;\n    const raakt =\n      !!obstakel &&\n      vlak.links < obstakel.rechts &&\n      vlak.rechts > obstakel.links &&\n      vlak.boven < obstakel.onder &&\n      vlak.onder > obstakel.boven;\n    return binnen && !raakt;\n  };\n  return VOORKEUR.find(past) ?? 'bottom';\n}\n","import { cogProtocol, setColorFunction } from '@geomatico/maplibre-cog-protocol';\nimport maplibregl from 'maplibre-gl';\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport Map, { Layer, Marker, NavigationControl, ScaleControl, Source } from 'react-map-gl/maplibre';\n\nimport CrossSectionTool from './CrossSectionTool';\nimport DiepteSpeld from './DiepteSpeld';\nimport GebiedTool from './GebiedTool';\nimport { useKaart, toontPeilsoort } from './KaartContext';\n\nimport type { KaartBounds } from '@/js/lib/dataset-viewport';\nimport type { OmgevingLezing } from '@/js/lib/depth-at-point';\nimport type { LineLayerSpecification } from 'maplibre-gl';\nimport type { MapLayerMouseEvent, MapRef } from 'react-map-gl/maplibre';\n\nimport OmgevingLagen from '@/js/components/kaart/OmgevingLagen';\nimport { useBaggervakken } from '@/js/hooks/use-baggervakken';\nimport {\n  getDisplayCogUrl,\n  isVoorspellingsDatum,\n  useReadyDatasets,\n  useAtpDatasets,\n} from '@/js/hooks/use-datasets';\nimport { useDiepteOpPunt, type Speldpunt } from '@/js/hooks/use-diepte-op-punt';\nimport { useDieptecijfers } from '@/js/hooks/use-dieptecijfers';\nimport { useDieptecontouren } from '@/js/hooks/use-dieptecontouren';\nimport { laagSleutel, tweedeCogUrl, useMaandLagen } from '@/js/hooks/use-maand-lagen';\nimport { useOmgevingKaarten } from '@/js/hooks/use-omgeving';\nimport { useOverlays } from '@/js/hooks/use-overlays';\nimport { useScheepsactiviteit } from '@/js/hooks/use-scheepsactiviteit';\nimport { applyClipToTile, BaggervakMask, getClipMask, setClipMask } from '@/js/lib/baggervak-mask';\nimport {\n  clearReferencePairing,\n  getReferencePairing,\n  renderDeltaTile,\n  setDeltaColormap,\n  setDeltaVerschil,\n  setReferencePairing,\n  setSpreiding,\n} from '@/js/lib/bathycog-delta';\nimport { cogSourceUrl } from '@/js/lib/cog-source';\nimport {\n  configForReferenceLevel,\n  makeColorFunction,\n  type ReferenceLevel,\n} from '@/js/lib/depth-colors';\nimport { dieptecijferKleurExpressie } from '@/js/lib/dieptecijfers';\nimport {\n  CONTOUR_INTERVAL_M,\n  CONTOUR_KLEUR,\n  CONTOUR_LABEL_KLEUR,\n  contourDikteExpressie,\n} from '@/js/lib/dieptecontouren';\nimport { displayTilesProtocol } from '@/js/lib/display-tiles-protocol';\nimport { geojsonBounds } from '@/js/lib/geojson-bounds';\nimport { registreerKaartBenchmark } from '@/js/lib/kaart-benchmark';\nimport { rasterResolutionFactor } from '@/js/lib/kaart-experiment';\nimport { objectVoorPunt, type KaartObject } from '@/js/lib/kaart-object';\nimport { ondergrondStijl, useGoogleOndergrond } from '@/js/lib/kaart-ondergrond';\nimport { LAAG_KLEUR } from '@/js/lib/laagkleuren';\nimport { mercatorToLngLat } from '@/js/lib/mercator';\nimport { omgevingVoorDatum, variantVoor } from '@/js/lib/omgeving';\nimport { ATP_OUTLINE_COLOR } from '@/js/lib/peilsoort';\nimport { renderReliefTile } from '@/js/lib/relief';\nimport { FASE_DEKKING, FASE_KLEUR, STRIJDIG_STREEP } from '@/js/lib/scheepsactiviteit';\nimport { shallowestColorExpression, shallowestPointsGeoJSON } from '@/js/lib/shallowest-points';\nimport { speldAnker, speldOffset, type SpeldAnker } from '@/js/lib/speld-anker';\nimport {\n  logTijdlijn,\n  meet,\n  registreerDiagnose,\n  startMeting,\n  tel,\n  tijdlijnMetingAan,\n} from '@/js/lib/tijdlijn-meting';\nimport { leesTijdlijnStatus } from '@/js/lib/tijdlijn-status';\nimport { TileRequestQueue } from '@/js/lib/tile-request-queue';\n\nimport 'maplibre-gl/dist/maplibre-gl.css';\n\nconst tileQueue = new TileRequestQueue(8, 2);\nconst cogFouten = new globalThis.Map<string, string>();\nregistreerDiagnose('tileQueue', () => tileQueue.snapshot());\n\n// Wrap cogProtocol: lower minzoom floor, apply baggervak clip mask, and — for\n// bathymetrie-COGs met een geregistreerde reference-COG (Sub 7 / #97) — per\n// pixel de reference-depth aftrekken en het resultaat als delta inkleuren.\nconst TILE_URL_RE = /cog:\\/\\/(.+)\\/(\\d+)\\/(\\d+)\\/(\\d+)/;\n\nfunction cogUrlHash(groupOne: string): string {\n  return groupOne.split('#')[1] ?? '';\n}\n\nfunction extractCogUrl(groupOne: string): string {\n  // De cog-protocol URL is ``cog://{sasUrl}#{hash}``. Voor de registry-lookup\n  // willen we alleen het SAS-URL deel zonder hash-fragment.\n  return groupOne.split('#')[0];\n}\n\n/** Korte naam van een fout, om hem in een teller te kunnen zetten. */\nfunction foutnaam(fout: unknown): string {\n  // Error.message kan een volledige SAS-URL bevatten; niet exporteren.\n  if (fout instanceof Error) return fout.name;\n  return 'onbekend';\n}\n\nconst renderCog: typeof cogProtocol = async (params) => {\n  if (params.type === 'image') {\n    const match = params.url.match(TILE_URL_RE);\n    if (match) {\n      const cogUrl = extractCogUrl(match[1]);\n      if (cogUrlHash(match[1]).startsWith('_relief')) {\n        const z = parseInt(match[2]);\n        const x = parseInt(match[3]);\n        const y = parseInt(match[4]);\n        try {\n          let bitmap = await meet('tile.relief', () => renderReliefTile(cogUrl, z, x, y));\n          const mask = getClipMask();\n          if (mask) bitmap = await applyClipToTile(bitmap, mask, z, x, y);\n          return { data: bitmap } as any;\n        } catch (fout) {\n          // Een schaduw die niet lukt is geen dieptefout: leeg laten.\n          tel(`reliëf mislukt: ${foutnaam(fout)}`);\n          return { data: await createImageBitmap(new ImageData(256, 256)) } as any;\n        }\n      }\n      const refUrl = getReferencePairing(cogUrl);\n      const z = parseInt(match[2]);\n      const x = parseInt(match[3]);\n      const y = parseInt(match[4]);\n      if (!refUrl && cogUrlHash(match[1]).includes('_kans')) {\n        // Een kans tegen één plat vlak per vak zou zich voordoen als de echte\n        // kans tegen het referentieraster. Zonder raster: niets tekenen.\n        tel('kanstegel zonder referentie');\n        return { data: await createImageBitmap(new ImageData(256, 256)) } as any;\n      }\n      if (!refUrl && cogUrlHash(match[1]).includes('=verschil')) {\n        // Verschilkaart zonder vorige peiling (eerste of jaarpeiling): het vak blijft\n        // leeg. Het library-pad hieronder zou de diepte zelf tekenen, en die\n        // zou zich dan voordoen als een verschil. Het vakpaneel zegt waarom.\n        tel('verschiltegel zonder vorige peiling');\n        return { data: await createImageBitmap(new ImageData(256, 256)) } as any;\n      }\n      if (refUrl) {\n        try {\n          tel('tile via deltapad');\n          let bitmap = await meet('tile.delta', () => renderDeltaTile(cogUrl, refUrl, z, x, y), {\n            z,\n            x,\n            y,\n          });\n          const mask = getClipMask();\n          if (mask) {\n            bitmap = await meet('tile.clip', () => applyClipToTile(bitmap, mask, z, x, y));\n          }\n          return { data: bitmap } as any;\n        } catch (fout) {\n          // Niets tekenen. De terugval hieronder kleurt tegen één plat vlak per\n          // vak in plaats van tegen het referentieraster; buiten de geul lopen\n          // die meters uiteen. Dat verscheen dan als een rechthoekig blok in\n          // een afwijkende kleur, ónder de legenda \"afstand tot het\n          // baggerreferentievlak\" — een andere berekening die zich voordeed als\n          // dezelfde. Een gat is eerlijker dan een verkeerd getal.\n          tel(`deltapad mislukt: ${foutnaam(fout)}`);\n          return { data: null } as any;\n        }\n      }\n    }\n  }\n\n  let result;\n  try {\n    if (params.type === 'image') tel('tile via library-pad');\n    if (params.type === 'json') tel('COG-header via library');\n    result = await meet(`cogProtocol (${params.type})`, () => cogProtocol(params));\n  } catch {\n    tel('cogProtocol: fout met leeg resultaat');\n    // COG read can fail at extreme zoom levels (typed array overflow in geotiff.js).\n    // Return empty response so the map stays functional.\n    return { data: null } as any;\n  }\n  if (params.type === 'json' && result.data) {\n    const natural = (result.data as any).minzoom ?? 0;\n    (result.data as any).minzoom = Math.min(natural, 8);\n  }\n  if (params.type === 'image') {\n    const mask = getClipMask();\n    if (mask) {\n      const match = params.url.match(TILE_URL_RE);\n      if (match) {\n        const z = parseInt(match[2]);\n        const x = parseInt(match[3]);\n        const y = parseInt(match[4]);\n        result.data = await meet('tile.clip', () =>\n          applyClipToTile(result.data as ImageBitmap, mask, z, x, y)\n        );\n      }\n    }\n  }\n  return result;\n};\nconst wrappedCogProtocol = (\n  params: Parameters<typeof cogProtocol>[0],\n  controller?: AbortController\n) => {\n  const wacht = startMeting('tile.wachtrij');\n  const match = params.type === 'image' ? params.url.match(TILE_URL_RE) : null;\n  const key = extractCogUrl(match ? match[1] : params.url.replace(/^cog:\\/\\//, ''));\n  return tileQueue\n    .schedule(\n      key,\n      async () => {\n        wacht();\n        try {\n          if (controller?.signal.aborted) throw new DOMException('Cancelled', 'AbortError');\n          const result = await renderCog(params);\n          if (controller?.signal.aborted) {\n            if (params.type === 'image' && result.data) (result.data as ImageBitmap).close();\n            throw new DOMException('Cancelled', 'AbortError');\n          }\n          if (!result.data) throw new Error('Raster kon niet worden geladen');\n          cogFouten.delete(params.url);\n          return result;\n        } catch (error) {\n          if (\n            !controller?.signal.aborted &&\n            !(error instanceof Error && error.name === 'AbortError')\n          ) {\n            cogFouten.set(params.url, key);\n          }\n          throw error;\n        }\n      },\n      controller?.signal\n    )\n    .finally(() => wacht('cancelled'));\n};\nmaplibregl.addProtocol('cog', wrappedCogProtocol);\nmaplibregl.addProtocol('pngtiles', displayTilesProtocol);\n\n/** Fallback view (centre of the Eemsgeul) tot de shapes geladen zijn. */\nconst INITIAL_VIEW_STATE = {\n  latitude: 53.614,\n  longitude: 6.44,\n  zoom: 14,\n};\n\n// Eén keer per page-load fitten op alle shapes. Module-level zodat SPA-navigatie\n// terug naar de kaart (reuseMaps) de camera van de gebruiker met rust laat;\n// een echte refresh reset de flag en fit opnieuw.\nlet didAutoFitBounds = false;\n\n/**\n * De verf van de scheepstracks, één keer beschreven omdat twee lagen hem\n * delen: de doorgetrokken lijnen en de gestreepte die de AIS tegenspreken.\n */\nconst SCHEEPSACTIVITEIT_VERF: LineLayerSpecification['paint'] = {\n  'line-color': [\n    'match',\n    ['get', 'fase'],\n    'baggeren',\n    FASE_KLEUR.baggeren,\n    'lossen',\n    FASE_KLEUR.lossen,\n    'varen',\n    FASE_KLEUR.varen,\n    // `onbekend`: de reizen reiken hier niet tot. Grijs, en de legenda in het\n    // laagpaneel zegt erbij waarom.\n    FASE_KLEUR.onbekend,\n  ],\n  'line-width': ['match', ['get', 'fase'], 'varen', 1.5, 2.5],\n  // Dekking per fase in plaats van één waarde voor alles: varen en onbekend\n  // zijn context, baggeren en lossen het onderwerp. Zie `FASE_DEKKING` voor\n  // de afweging.\n  'line-opacity': [\n    'match',\n    ['get', 'fase'],\n    'baggeren',\n    FASE_DEKKING.baggeren,\n    'lossen',\n    FASE_DEKKING.lossen,\n    'varen',\n    FASE_DEKKING.varen,\n    FASE_DEKKING.onbekend,\n  ],\n};\n\ninterface CogUrlEntry {\n  tileUrls?: Record<string, string>;\n  id: number;\n  url: string;\n  refLevel: ReferenceLevel;\n}\n\ninterface MapViewProps {\n  /** Wat er op het geklikte punt ligt, of null als daar geen vak of vorm is. */\n  onObjectClick: (object: KaartObject | null) => void;\n  mapRef: React.RefObject<MapRef | null>;\n}\n\nexport default function MapView({ onObjectClick, mapRef }: MapViewProps) {\n  const { state, dispatch } = useKaart();\n  const [isMapLoaded, setIsMapLoaded] = useState(false);\n  const benchmarkState = useRef(state);\n  benchmarkState.current = state;\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (map && isMapLoaded) return registreerKaartBenchmark(map, () => benchmarkState.current);\n  }, [mapRef, isMapLoaded]);\n  const [mapError, setMapError] = useState<string | null>(null);\n  const [atpCogUrls, setAtpCogUrls] = useState<CogUrlEntry[]>([]);\n\n  const { geojson: baggervakken, loading: baggervakkenLoading } = useBaggervakken();\n  const { geojson: vaargeul, loading: vaargeulLoading } = useOverlays('vaargeul');\n  const { geojson: randstroken, loading: randstrokenLoading } = useOverlays('randstrook');\n  const { geojson: passeerstroken, loading: passeerstrokenLoading } = useOverlays('passeerstrook');\n  const { geojson: taluds, loading: taludsLoading } = useOverlays('talud');\n  const { geojson: stortlocaties, loading: stortlocatiesLoading } = useOverlays('stortlocatie');\n  const {\n    geojson: scheepsactiviteit,\n    fout: scheepsactiviteitFout,\n    melding: scheepsactiviteitMelding,\n  } = useScheepsactiviteit(\n    state.availableDates,\n    state.selectedDate,\n    state.layers.scheepsactiviteit\n  );\n  useEffect(() => {\n    dispatch({ type: 'SET_LAAGFOUT', laag: 'scheepsactiviteit', fout: scheepsactiviteitFout });\n  }, [dispatch, scheepsactiviteitFout]);\n  useEffect(() => {\n    dispatch({\n      type: 'SET_LAAGMELDING',\n      laag: 'scheepsactiviteit',\n      melding: scheepsactiviteitMelding,\n    });\n  }, [dispatch, scheepsactiviteitMelding]);\n  const { datasets } = useReadyDatasets(state.selectedDate, state.retentionMonths);\n  const { datasets: atpDatasets } = useAtpDatasets(\n    state.selectedDate,\n    state.retentionMonths,\n    toontPeilsoort(state, 'atp')\n  );\n\n  // De datasets zijn al `latest_per_vak`, dus dit is precies een punt per vak.\n  const ondiepsteGeoJson = useMemo(\n    () =>\n      state.layers.ondiepstePunten\n        ? shallowestPointsGeoJSON(datasets)\n        : { type: 'FeatureCollection' as const, features: [] },\n    [datasets, state.layers.ondiepstePunten]\n  );\n\n  // Het deltapad kleurt zijn tegels buiten React om — de tile-wrapper krijgt\n  // alleen een URL mee. De keuze moet daar dus apart heen, en vóór de effecten\n  // die de COG-bronnen opnieuw opbouwen; effecten draaien op volgorde van\n  // declaratie, vandaar dat deze bovenaan staat.\n  useEffect(() => {\n    setDeltaColormap(state.colormap);\n  }, [state.colormap]);\n  useEffect(() => {\n    setDeltaVerschil(state.referenceLevel === 'verschil');\n  }, [state.referenceLevel]);\n\n  // De verschilkaart kleurt de verandering; cijfers bij een verandering zouden\n  // tegen niets afgezet zijn. Daar tonen de cijfers de diepte in NAP.\n  const cijferReferentie = state.referenceLevel === 'verschil' ? 'nap' : state.referenceLevel;\n\n  // Dieptecijfers (#114). Wordt op de server gerekend, dus alleen ophalen als\n  // de laag daadwerkelijk aanstaat.\n  const {\n    geojson: dieptecijfersGeoJson,\n    laden: dieptecijfersLaden,\n    zonderReferentieRaster,\n  } = useDieptecijfers({\n    datasets,\n    referentie: cijferReferentie,\n    enabled: state.layers.dieptecijfers && state.peilsoorten.length > 0,\n  });\n\n  // Dieptecontouren (#114). Alleen tegen een peil; de hook haalt bij een\n  // ontwerpdiepte niets op.\n  const { geojson: contourenGeoJson, laden: contourenLaden } = useDieptecontouren({\n    datasets,\n    referentie: state.referenceLevel,\n    interval: CONTOUR_INTERVAL_M,\n    enabled: state.layers.dieptecontouren && state.peilsoorten.length > 0,\n  });\n\n  // De cijfers en contouren worden op de server uit de COG's gelezen; dat duurt\n  // bij een volle kaart een paar seconden. Zonder melding lijkt de laag stuk in\n  // plaats van bezig. Het werkblad zet het onder de laag, niet over de kaart.\n  //\n  // Zonder referentieraster valt het cijfer terug op één niveau per vak. Op een\n  // talud loopt het vlak binnen een vak op, dus dat leest daar structureel\n  // verkeerd (#99). Beter gezegd dan verzwegen.\n  const dieptecijfersMelding = !state.layers.dieptecijfers\n    ? null\n    : dieptecijfersLaden\n      ? 'Dieptecijfers berekenen…'\n      : zonderReferentieRaster === 1\n        ? 'Eén vak heeft geen referentieraster; daar telt het cijfer tegen één niveau voor het hele vak.'\n        : zonderReferentieRaster > 1\n          ? `${zonderReferentieRaster} vakken hebben geen referentieraster; daar telt het cijfer tegen één niveau voor het hele vak.`\n          : null;\n  const contourenMelding =\n    state.layers.dieptecontouren && contourenLaden ? 'Dieptecontouren berekenen…' : null;\n  useEffect(() => {\n    dispatch({ type: 'SET_LAAGMELDING', laag: 'dieptecijfers', melding: dieptecijfersMelding });\n  }, [dispatch, dieptecijfersMelding]);\n  useEffect(() => {\n    dispatch({ type: 'SET_LAAGMELDING', laag: 'dieptecontouren', melding: contourenMelding });\n  }, [dispatch, contourenMelding]);\n\n  // Omgeving actueel (ontwerp 2026-10-14): de kaart bij deze tijdlijnstap, nooit\n  // vooruit. De lijst gaat ook naar de context, voor legenda en tijdlijnstippen.\n  const omgevingKaarten = useOmgevingKaarten(state.layers.omgeving);\n  useEffect(() => {\n    dispatch({ type: 'SET_OMGEVING_KAARTEN', kaarten: omgevingKaarten });\n  }, [dispatch, omgevingKaarten]);\n  const omgevingVariant = variantVoor(state.referenceLevel);\n  const omgevingDoel = useMemo(\n    () =>\n      state.layers.omgeving\n        ? omgevingVoorDatum(\n            omgevingKaarten,\n            state.selectedDate,\n            isVoorspellingsDatum(state.selectedDate),\n            omgevingVariant\n          )\n        : null,\n    [omgevingKaarten, state.layers.omgeving, state.selectedDate, omgevingVariant]\n  );\n  const omgevingBronGeladen = useCallback(\n    (id: string) => {\n      const map = mapRef.current?.getMap();\n      try {\n        return !!map?.getSource(id) && map.isSourceLoaded(id);\n      } catch {\n        return false;\n      }\n    },\n    [mapRef]\n  );\n  const omgevingLezing = useMemo<OmgevingLezing | null>(\n    () =>\n      omgevingDoel\n        ? {\n            diepteUrl: omgevingDoel.diepte_url,\n            zekerheidUrl: omgevingDoel.zekerheid_url,\n            bronnen: omgevingDoel.bronnen,\n            verwacht: omgevingDoel.soort === 'voorspelling',\n          }\n        : null,\n    [omgevingDoel]\n  );\n\n  // Klik = diepte (ontwerp 2026-10-15, §5): een speld op het punt van de klik.\n  // De waarde loopt met de tijdlijn mee; het punt blijft staan.\n  const [speld, setSpeld] = useState<{ punt: Speldpunt; anker: SpeldAnker } | null>(null);\n  // De vormen rond de vakken: een klik op de passeerstrook hoort bij het vak\n  // ernaast, niet \"buiten de vakken\".\n  const vormen = useMemo(\n    () => ({ randstroken, taluds, passeerstroken, stortlocaties }),\n    [randstroken, taluds, passeerstroken, stortlocaties]\n  );\n  const speldReading = useDiepteOpPunt({\n    punt: speld?.punt ?? null,\n    datasets,\n    baggervakken,\n    referenceLevel: state.referenceLevel,\n    omgeving: omgevingLezing,\n    vormen,\n  });\n  const kanSpeldLezen = state.peilsoorten.length > 0 || !!omgevingLezing;\n  const tekent = state.crossSection.drawing || state.gebied.drawing;\n\n  // Bij het begin van een profiel of gebied gaat de speld weg: hij ligt anders\n  // over het stuk geul waar getekend wordt, en een klik op zijn kruisje zou ook\n  // een hoekpunt zetten (de gereedschappen luisteren zelf naar de kaart).\n  useEffect(() => {\n    if (tekent) setSpeld(null);\n  }, [tekent]);\n\n  useEffect(() => {\n    if (!speld) return;\n    const opToets = (e: KeyboardEvent) => {\n      if (e.key !== 'Escape') return;\n      // Esc die een keuzelijst, menu of venster sluit, is daarvan; Radix zet\n      // dan defaultPrevented. De doelcontrole vangt wat dat niet doet.\n      const doel = e.target as Element | null;\n      if (\n        e.defaultPrevented ||\n        doel?.closest?.(\n          '[role=\"listbox\"],[role=\"menu\"],[role=\"dialog\"],[data-radix-popper-content-wrapper]'\n        )\n      )\n        return;\n      setSpeld(null);\n    };\n    window.addEventListener('keydown', opToets);\n    return () => window.removeEventListener('keydown', opToets);\n  }, [speld]);\n\n  const ondergrond = useGoogleOndergrond(state.basemap);\n  const mapStyle = useMemo(() => ondergrondStijl(ondergrond), [ondergrond]);\n\n  // Fit de init-view op de bbox van alle shapes zodra ze geladen zijn (i.p.v.\n  // de hardcoded fallback-view). Wacht tot alle lagen klaar zijn met laden\n  // (fout ⇒ loading=false met geojson=null, telt gewoon niet mee in de bbox).\n  const shapesLoading =\n    baggervakkenLoading ||\n    vaargeulLoading ||\n    randstrokenLoading ||\n    passeerstrokenLoading ||\n    taludsLoading ||\n    stortlocatiesLoading;\n\n  useEffect(() => {\n    if (didAutoFitBounds || !isMapLoaded || shapesLoading) return;\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    const bounds = geojsonBounds(\n      baggervakken,\n      vaargeul,\n      randstroken,\n      passeerstroken,\n      taluds,\n      stortlocaties\n    );\n    if (!bounds) return;\n\n    didAutoFitBounds = true;\n    map.fitBounds(bounds, {\n      padding: 64,\n      maxZoom: 15,\n      duration: 0,\n    });\n  }, [\n    isMapLoaded,\n    shapesLoading,\n    baggervakken,\n    vaargeul,\n    randstroken,\n    passeerstroken,\n    taluds,\n    stortlocaties,\n    mapRef,\n  ]);\n\n  // Build lookup: baggervak code → reference levels\n  type VakRef = {\n    ngd: number | null;\n    ohd: number | null;\n    lat: number | null;\n    sigma: number | null;\n  };\n  const vakRefLookup = useMemo(() => {\n    const map: Record<string, VakRef> = {};\n    if (!baggervakken) return map;\n    for (const f of baggervakken.features) {\n      const code = f.properties?.code;\n      if (code) {\n        map[code as string] = {\n          ngd: (f.properties?.ngd_level as number | null) ?? null,\n          ohd: (f.properties?.ohd_level as number | null) ?? null,\n          lat: (f.properties?.lat_level as number | null) ?? null,\n          sigma: (f.properties?.spreiding_maand_m as number | null) ?? null,\n        };\n      }\n    }\n    return map;\n  }, [baggervakken]);\n\n  // Rasters voegen zich met `beforeId` onder `overlay-separator` in, en MapLibre\n  // weigert een laag waarvan de `beforeId` nog niet bestaat — met een fout die\n  // hier als fatale kaartfout eindigt (wit vlak). Of de scheiding er al is,\n  // hangt af van wanneer react-map-gl hem toevoegt, en dat valt niet af te\n  // leiden uit onze eigen `isMapLoaded`. Dus vragen we het de kaart zelf, en\n  // tekenen we pas rasters als het antwoord ja is.\n  const [separatorGereed, setSeparatorGereed] = useState(false);\n  useEffect(() => {\n    if (!isMapLoaded || separatorGereed) return;\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    const aanwezig = () => {\n      if (\n        !map.getLayer?.('overlay-separator') ||\n        !map.getLayer?.('relief-separator') ||\n        !map.getLayer?.('omgeving-separator')\n      )\n        return false;\n      setSeparatorGereed(true);\n      return true;\n    };\n    if (aanwezig()) return;\n\n    const id = setInterval(() => {\n      if (aanwezig()) clearInterval(id);\n    }, 50);\n    return () => clearInterval(id);\n  }, [isMapLoaded, separatorGereed, mapRef]);\n\n  // Het kaartbeeld, als grens voor welke COG's op de kaart staan. Bijgehouden\n  // op `moveend`: tijdens het pannen zelf hoeft er niets te wisselen, dat mag\n  // rustig achterlopen.\n  const [beeld, setBeeld] = useState<KaartBounds | null>(null);\n  const leesBeeld = useCallback(() => {\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n\n    const b = map.getBounds();\n    const nieuw = {\n      west: b.getWest(),\n      south: b.getSouth(),\n      east: b.getEast(),\n      north: b.getNorth(),\n    };\n    // Alleen bij een échte verandering een nieuw object. Elke `moveend` gaf\n    // anders een verse identiteit, ook bij hetzelfde beeld, en daarmee begon\n    // het klaarzetten van de maandlagen telkens opnieuw — het kwam zo nooit\n    // verder dan de buurmaanden.\n    setBeeld((vorig) =>\n      vorig &&\n      vorig.west === nieuw.west &&\n      vorig.south === nieuw.south &&\n      vorig.east === nieuw.east &&\n      vorig.north === nieuw.north\n        ? vorig\n        : nieuw\n    );\n  }, [mapRef]);\n\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !isMapLoaded) return;\n    // Eén keer meteen: bij `reuseMaps` is de kaart al uitgelijnd en komt er\n    // geen `moveend` meer. De auto-fit hieronder vuurt hem wél.\n    leesBeeld();\n    map.on('moveend', leesBeeld);\n    return () => {\n      map.off('moveend', leesBeeld);\n    };\n  }, [isMapLoaded, mapRef, leesBeeld]);\n\n  // Readiness must advance even when an unrelated source prevents map-idle.\n  const [idleTik, setIdleTik] = useState(0);\n  const [beweegt, setBeweegt] = useState(false);\n  const [toestemming, setToestemming] = useState({\n    context: '',\n    geselecteerd: false,\n    alles: false,\n  });\n  const laadContext = JSON.stringify([\n    beeld,\n    state.selectedDate,\n    state.referenceLevel,\n    state.retentionMonths,\n    state.colormap,\n    state.clipToBaggervakken,\n  ]);\n  const regulier = toontPeilsoort(state, 'regulier');\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !isMapLoaded) return;\n    const start = () => setBeweegt(true);\n    const stop = () => {\n      setBeweegt(false);\n      setIdleTik((n) => n + 1);\n    };\n    // A completed source should release the next date immediately. Polling\n    // alone adds up to 250 ms per admission, even with all bytes on localhost.\n    // Coalesce completions into one frame; normal readiness/priority checks\n    // still decide whether any new background sources may be mounted.\n    let completionFrame: number | null = null;\n    const sourceComplete = (event: { sourceId?: string; isSourceLoaded?: boolean }) => {\n      if (!event.sourceId?.startsWith('cog-') || !event.isSourceLoaded) return;\n      if (completionFrame !== null) return;\n      completionFrame = window.requestAnimationFrame(() => {\n        completionFrame = null;\n        setIdleTik((n) => n + 1);\n      });\n    };\n    map.on('movestart', start);\n    map.on('moveend', stop);\n    map.on('sourcedata', sourceComplete);\n    const timer = window.setInterval(() => setIdleTik((n) => n + 1), 250);\n    return () => {\n      map.off('movestart', start);\n      map.off('moveend', stop);\n      map.off('sourcedata', sourceComplete);\n      if (completionFrame !== null) window.cancelAnimationFrame(completionFrame);\n      window.clearInterval(timer);\n    };\n  }, [isMapLoaded, mapRef]);\n\n  const maandLagen = useMaandLagen({\n    datums: state.availableDates,\n    huidigeDatum: state.selectedDate,\n    retentionMonths: state.retentionMonths,\n    referenceLevel: state.referenceLevel,\n    beeld,\n    tik: idleTik,\n    vooruitladen: toestemming.context === laadContext && toestemming.alles && !beweegt,\n    metadataVooruitladen:\n      toestemming.context === laadContext && toestemming.geselecteerd && !beweegt,\n    beweegt,\n    enabled: toontPeilsoort(state, 'regulier'),\n    vakRefLookup,\n    colormap: state.colormap,\n    clip: state.clipToBaggervakken,\n  });\n  const actieveLagen = useMemo(\n    () =>\n      maandLagen.lagen.filter((l) => maandLagen.actieveSleutels.has(laagSleutel(l.id, l.refLevel))),\n    [maandLagen]\n  );\n\n  // Alleen tijdens een meetopname. Dit leest ook door als de idle-keten\n  // stilstaat, zonder zelf een laadtik of React-update te veroorzaken.\n  const maandDiagnoseRef = useRef(maandLagen);\n  maandDiagnoseRef.current = maandLagen;\n  useEffect(() => {\n    if (!tijdlijnMetingAan || !isMapLoaded) return;\n    const map = mapRef.current?.getMap();\n    if (!map) return;\n    const datum = state.selectedDate;\n    const klaar = startMeting('kaart.selectie-tot-geladen-render', {\n      datum,\n      referentie: state.referenceLevel,\n      retentionMonths: state.retentionMonths,\n      zoom: map.getZoom(),\n    });\n    logTijdlijn('kaart.context', { datum, ...beeld });\n    const lees = () => {\n      const maanden = maandDiagnoseRef.current;\n      const bronnen = maanden.lagen.map((laag) => {\n        const sleutel = laagSleutel(laag.id, laag.refLevel);\n        const id = `cog-${sleutel}`;\n        let geladen = false;\n        const gemonteerd = !!map.getSource(id);\n        try {\n          geladen = gemonteerd && map.isSourceLoaded(id);\n        } catch {\n          /* bron wisselt */\n        }\n        return { id, actief: maanden.actieveSleutels.has(sleutel), gemonteerd, geladen };\n      });\n      const selectieOpgelost = !!datum && maanden.klareDatums.includes(datum);\n      const actiefGeladen = selectieOpgelost && bronnen.every((b) => !b.actief || b.geladen);\n      return {\n        datum,\n        selectieOpgelost,\n        actiefGeladen,\n        maandenOpgelost: maanden.klareDatums.length,\n        maandenTotaal: state.availableDates.length,\n        achtergrondBezig: maanden.bezigMetKlaarzetten || bronnen.some((b) => !b.geladen),\n        stijlGeladen: !!map.isStyleLoaded(),\n        alleTegelsGeladen: map.areTilesLoaded(),\n        bronnen,\n      };\n    };\n    const afmelden = registreerDiagnose('kaart', lees);\n    let laatsteStatus = '';\n    let renderGemeld = false;\n    const neemStatus = () => {\n      const { bronnen, ...status } = lees();\n      const detail = {\n        ...status,\n        bronnen: bronnen.length,\n        actieveBronnen: bronnen.filter((b) => b.actief).length,\n        wachtendeBronnen: bronnen.filter((b) => !b.geladen).length,\n      };\n      const sleutel = JSON.stringify(detail);\n      if (sleutel !== laatsteStatus) {\n        logTijdlijn('kaart.status', detail);\n        laatsteStatus = sleutel;\n      }\n    };\n    const opRender = () => {\n      if (!renderGemeld && lees().actiefGeladen) {\n        renderGemeld = true;\n        klaar();\n      }\n    };\n    const opError = () => logTijdlijn('kaart.error');\n    map.on('render', opRender);\n    map.on('error', opError);\n    neemStatus();\n    const timer = window.setInterval(neemStatus, 250);\n    return () => {\n      klaar('superseded');\n      afmelden();\n      window.clearInterval(timer);\n      map.off('render', opRender);\n      map.off('error', opError);\n    };\n  }, [\n    isMapLoaded,\n    mapRef,\n    beeld,\n    state.selectedDate,\n    state.referenceLevel,\n    state.retentionMonths,\n    state.colormap,\n    state.clipToBaggervakken,\n    state.availableDates,\n  ]);\n\n  // Install priorities before react-map-gl mounts its passive Source effects.\n  useLayoutEffect(() => {\n    const foreground = maandLagen.lagen\n      .filter((l) => maandLagen.actieveSleutels.has(laagSleutel(l.id, l.refLevel)))\n      .map((l) => l.url);\n    const background = maandLagen.lagen\n      .filter((l) => !maandLagen.actieveSleutels.has(laagSleutel(l.id, l.refLevel)))\n      .map((l) => l.url);\n    // Submitted protocol requests must keep progressing: MapLibre counts them\n    // against its own image limit even while they wait in our queue.\n    tileQueue.setPlan(foreground, background, {\n      foregroundReady: !beweegt && toestemming.context === laadContext && toestemming.geselecteerd,\n    });\n  }, [maandLagen, beweegt, toestemming, laadContext]);\n  useEffect(() => () => tileQueue.setPlan([], []), []);\n\n  const gemeldeTijdlijnRef = useRef('');\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    const urls = new globalThis.Map(\n      maandLagen.lagen.map((l) => [laagSleutel(l.id, l.refLevel), l.url])\n    );\n    const mislukteUrls = new Set(cogFouten.values());\n    const bronGeladen = (sleutel: string) => {\n      const id = `cog-${sleutel}`;\n      try {\n        return !!map?.getSource(id) && map.isSourceLoaded(id);\n      } catch {\n        return false;\n      }\n    };\n    const status = leesTijdlijnStatus(\n      maandLagen,\n      state.selectedDate,\n      bronGeladen,\n      (sleutel) => mislukteUrls.has(urls.get(sleutel) ?? ''),\n      regulier\n    );\n    const geselecteerd = !!state.selectedDate && status.geladenDatums.includes(state.selectedDate);\n    // A failed background month is not ready, but must not block preparation\n    // of every subsequent date. Only genuinely pending sources hold admission.\n    const alles =\n      geselecteerd && !maandLagen.klareDatums.some((d) => status.ladendeDatums.includes(d));\n    setToestemming((old) =>\n      old.context === laadContext && old.geselecteerd === geselecteerd && old.alles === alles\n        ? old\n        : { context: laadContext, geselecteerd, alles }\n    );\n    const handtekening = JSON.stringify(status);\n    if (gemeldeTijdlijnRef.current === handtekening) return;\n    gemeldeTijdlijnRef.current = handtekening;\n    logTijdlijn('tijdlijn.indicator', {\n      geladen: status.geladenDatums.length,\n      geselecteerdBezig: status.geselecteerdBezig,\n      achtergrondBezig: status.achtergrondBezig,\n      fout: status.fout,\n    });\n    dispatch({ type: 'SET_TIJDLIJN_STATUS', ...status });\n  }, [dispatch, mapRef, maandLagen, idleTik, state.selectedDate, laadContext, regulier]);\n\n  // reuseMaps: onLoad won't fire when MapLibre reuses a cached instance.\n  // Poll briefly until the map ref is available, then check if already loaded.\n  useEffect(() => {\n    if (isMapLoaded) return;\n\n    const tryResolve = () => {\n      const map = mapRef.current?.getMap();\n      if (!map) return false;\n      if (map.isStyleLoaded()) {\n        setIsMapLoaded(true);\n        return true;\n      }\n      return false;\n    };\n\n    if (tryResolve()) return;\n\n    // Poll every 100ms for up to 3s (covers mapRef becoming available + style loading)\n    const interval = setInterval(() => {\n      if (tryResolve()) clearInterval(interval);\n    }, 100);\n    const timeout = setTimeout(() => {\n      clearInterval(interval);\n      setIsMapLoaded(true); // final fallback\n    }, 3000);\n\n    return () => {\n      clearInterval(interval);\n      clearTimeout(timeout);\n    };\n  }, [isMapLoaded, mapRef]);\n\n  // Resolve ATP COG URLs — no double-buffering (user-initiated toggle, infrequent).\n  // Reference-pairing werkt identiek aan het reguliere bathymetrie-pad.\n  useEffect(() => {\n    if (!toontPeilsoort(state, 'atp') || atpDatasets.length === 0) {\n      setAtpCogUrls([]);\n      return;\n    }\n    let cancelled = false;\n\n    (async () => {\n      const refLevel = state.referenceLevel;\n      const results = (\n        await Promise.all(\n          atpDatasets.map(async (ds): Promise<CogUrlEntry | null> => {\n            const url = await getDisplayCogUrl(ds.id);\n            if (!url) return null;\n            const refs = vakRefLookup[ds.baggervak_code];\n            const dsConfig = configForReferenceLevel(\n              refLevel,\n              refs?.ngd,\n              // Geen IVD op de kaart (bestek 2026); de parameter blijft voor de tegelrenderer.\n              null,\n              refs?.ohd,\n              refs?.lat,\n              state.colormap\n            );\n            setColorFunction(url, makeColorFunction(dsConfig));\n            setSpreiding(url, refs?.sigma);\n            // Zelfde volgorde als bij de reguliere peilingen hierboven. LAT is\n            // een peil en geen ontwerpdiepte: geen tweede COG (#114). Bij de\n            // verschilkaart is dat de vorige reguliere peiling van het vak.\n            const refUrl = await tweedeCogUrl(ds, refLevel);\n            if (cancelled) return null;\n            if (refUrl) setReferencePairing(url, refUrl);\n            else clearReferencePairing(url);\n            return {\n              id: ds.id,\n              url,\n              refLevel,\n              tileUrls: (ds as typeof ds & { display_tile_urls?: Record<string, string> })\n                .display_tile_urls,\n            };\n          })\n        )\n      ).filter((r): r is CogUrlEntry => r !== null);\n      if (!cancelled) setAtpCogUrls(results);\n    })();\n\n    return () => {\n      cancelled = true;\n    };\n  }, [\n    atpDatasets,\n    toontPeilsoort(state, 'atp'),\n    state.referenceLevel,\n    state.colormap,\n    vakRefLookup,\n  ]);\n\n  // Clip mask lifecycle — includes baggervakken + stortlocaties\n  useEffect(() => {\n    if (state.clipToBaggervakken && baggervakken) {\n      const collections = [baggervakken];\n      if (stortlocaties) collections.push(stortlocaties);\n      setClipMask(new BaggervakMask(...collections));\n    } else {\n      setClipMask(null);\n    }\n    return () => setClipMask(null);\n  }, [state.clipToBaggervakken, baggervakken, stortlocaties]);\n\n  // 3D terrain: add/remove terrain source when is3D changes\n  useEffect(() => {\n    const map = mapRef.current?.getMap();\n    if (!map || !isMapLoaded || !map.isStyleLoaded()) return;\n\n    if (state.is3D && actieveLagen.length > 0) {\n      // Use the first COG as a DEM source with #dem suffix\n      const demUrl = `cog://${actieveLagen[0].url}#dem`;\n      if (!map.getSource('terrain-dem')) {\n        map.addSource('terrain-dem', {\n          type: 'raster-dem',\n          url: demUrl,\n          tileSize: 256,\n        });\n      }\n      map.setTerrain({ source: 'terrain-dem', exaggeration: 3 });\n    } else {\n      map.setTerrain(null);\n      if (map.getSource('terrain-dem')) {\n        map.removeSource('terrain-dem');\n      }\n    }\n  }, [state.is3D, actieveLagen, isMapLoaded, mapRef]);\n\n  const handleClick = useCallback(\n    (e: MapLayerMouseEvent) => {\n      // Tijdens het tekenen is een klik een punt van het gereedschap, geen\n      // speld of vakkeuze: anders springt het werkblad bij elk hoekpunt naar Vak.\n      if (tekent) return;\n      // De speld ligt in de kaartcontainer, dus MapLibre ziet een klik op het\n      // kruisje ook als kaartklik. Die mag de speld niet meteen opnieuw zetten.\n      if ((e.originalEvent?.target as Element | null)?.closest?.('[data-speld]')) return;\n\n      if (kanSpeldLezen) {\n        const kaart = e.target.getContainer?.();\n        const kaartVlak = kaart?.getBoundingClientRect();\n        // De werkbalk ligt rechtsboven over de kaart; een label eronder is\n        // niet te lezen.\n        const balk = kaart?.ownerDocument\n          .querySelector('[role=\"toolbar\"][aria-label=\"Meetgereedschap\"]')\n          ?.getBoundingClientRect();\n        setSpeld({\n          punt: { lng: e.lngLat.lng, lat: e.lngLat.lat, zoom: e.target.getZoom() },\n          anker: speldAnker(\n            e.point ?? { x: 0, y: 0 },\n            { breedte: kaart?.clientWidth ?? 0, hoogte: kaart?.clientHeight ?? 0 },\n            balk && kaartVlak\n              ? {\n                  links: balk.left - kaartVlak.left,\n                  rechts: balk.right - kaartVlak.left,\n                  boven: balk.top - kaartVlak.top,\n                  onder: balk.bottom - kaartVlak.top,\n                }\n              : null\n          ),\n        });\n      }\n      // Dezelfde bepaling als de speld, niet de laag onder de muis: die is er\n      // niet als de laag uit staat, en dan zou de speld een vak noemen dat het\n      // werkblad niet opent. Ook een klik ernaast telt: dan blijft er geen oud\n      // vak in het werkblad staan dat niets met de speld te maken heeft.\n      onObjectClick(objectVoorPunt(e.lngLat.lng, e.lngLat.lat, { baggervakken, ...vormen }));\n    },\n    [onObjectClick, tekent, kanSpeldLezen, baggervakken, vormen]\n  );\n\n  // Magenta fill + glow + dashed outline around each ATP's raster bbox.\n  // ATP bbox_* fields are in EPSG:3857 (set by the backend COG writer);\n  // maplibre-gl expects [lng, lat] WGS84 for GeoJSON sources, so we convert.\n  const atpBboxGeoJson = useMemo<GeoJSON.FeatureCollection | null>(() => {\n    if (!toontPeilsoort(state, 'atp') || atpDatasets.length === 0) return null;\n    const features: GeoJSON.Feature[] = [];\n    for (const ds of atpDatasets) {\n      const { bbox_minx, bbox_miny, bbox_maxx, bbox_maxy } = ds;\n      if (bbox_minx == null || bbox_miny == null || bbox_maxx == null || bbox_maxy == null) {\n        continue;\n      }\n      const ll = mercatorToLngLat(bbox_minx, bbox_miny);\n      const lr = mercatorToLngLat(bbox_maxx, bbox_miny);\n      const ur = mercatorToLngLat(bbox_maxx, bbox_maxy);\n      const ul = mercatorToLngLat(bbox_minx, bbox_maxy);\n      features.push({\n        type: 'Feature',\n        properties: { id: ds.id, code: ds.baggervak_code },\n        geometry: {\n          type: 'Polygon',\n          coordinates: [[ll, lr, ur, ul, ll]],\n        },\n      });\n    }\n    return { type: 'FeatureCollection', features };\n  }, [atpDatasets, toontPeilsoort(state, 'atp')]);\n\n  return (\n    <div className=\"h-full w-full\">\n      {!isMapLoaded && !mapError && (\n        <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm\">\n          <p className=\"text-sm text-muted-foreground\">Kaart wordt geladen...</p>\n        </div>\n      )}\n      {mapError && (\n        <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-background/85 px-6 text-center\">\n          <p className=\"text-sm text-destructive\">{mapError}</p>\n        </div>\n      )}\n\n      <Map\n        ref={mapRef}\n        initialViewState={INITIAL_VIEW_STATE}\n        mapStyle={mapStyle}\n        keyboard={false}\n        reuseMaps\n        style={{ width: '100%', height: '100%' }}\n        interactiveLayerIds={\n          state.layers.baggervakken && !state.crossSection.drawing ? ['baggervak-fill'] : []\n        }\n        onClick={handleClick}\n        onLoad={() => setIsMapLoaded(true)}\n        onError={(event) => {\n          // Raster errors belong to the timeline status, not a fatal blank map.\n          const sourceId = (event as unknown as { sourceId?: string }).sourceId;\n          if (sourceId?.startsWith('omgeving-')) {\n            // Niet fataal, maar wel geteld: de browsercheck (Taak 35) leest `omgeving-fout`.\n            tel('omgeving-fout');\n            return;\n          }\n          if (event.error?.name === 'AbortError' || sourceId?.startsWith('cog-')) return;\n          setIsMapLoaded(false);\n          setMapError(event.error?.message || 'Kaart kon niet worden geladen.');\n        }}\n      >\n        {/* Onderste scheiding: de omgeving (Omgeving actueel) voegt zich hieronder in,\n            dus onder elke peiling. Eerste kind, om dezelfde reden als hieronder. */}\n        <Layer id=\"omgeving-separator\" type=\"background\" paint={{ 'background-opacity': 0 }} />\n        {/* Onzichtbare scheiding tussen de rasters en de vectorlagen: de rasters\n            voegen zich met `beforeId` hieronder in, alles wat later gemonteerd\n            wordt komt erboven.\n\n            Staat bewust vooraan, direct na de omgevingsscheiding. MapLibre\n            weigert een laag met een `beforeId` die nog niet bestaat, en React\n            monteert kinderen op\n            volgorde van de boom — stond deze verderop, dan hing het ervan af of\n            de rasters toevallig een commit later verschenen dan de scheiding.\n            Dat ging jarenlang goed en brak zodra die timing verschoof. */}\n        <Layer id=\"overlay-separator\" type=\"background\" paint={{ 'background-opacity': 0 }} />\n        {/* Tweede scheiding, direct boven de eerste: de reliëflagen (WP6) voegen\n            zich hieronder in. Zo liggen ze boven elke dieptelaag, ook boven een\n            maand die later gemonteerd wordt, en onder de vakgrenzen. */}\n        <Layer id=\"relief-separator\" type=\"background\" paint={{ 'background-opacity': 0 }} />\n\n        {isMapLoaded && separatorGereed && state.layers.omgeving && (\n          <OmgevingLagen\n            kaarten={omgevingKaarten}\n            doel={omgevingDoel}\n            variant={omgevingVariant}\n            tik={idleTik}\n            isGeladen={omgevingBronGeladen}\n            beforeId=\"omgeving-separator\"\n            // Eis 1: de peilingen eerst. Pas als die klaarstaan laadt de omgeving vooruit.\n            vooruitladen={!beweegt && !maandLagen.bezigMetKlaarzetten}\n          />\n        )}\n        {/* Alle voorbereide datums blijven gemonteerd.\n            Zonder opacity-overgang mengt snel scrubben geen peilingen.\n            Hash fragment in URL busts MapLibre's tile cache on reference level change;\n            the COG protocol strips it before fetching (cogProtocol.js:14-17). */}\n        {isMapLoaded &&\n          separatorGereed &&\n          toontPeilsoort(state, 'regulier') &&\n          maandLagen.lagen.map(({ id, url, refLevel, tileUrls }) => (\n            <Source\n              key={`${id}-${refLevel}-${state.colormap}`}\n              id={`cog-${id}-${refLevel}`}\n              type=\"raster\"\n              url={cogSourceUrl(url, {\n                tileUrl:\n                  tileUrls?.[\n                    `${refLevel}-${refLevel === 'nap' || refLevel === 'lat' ? 'continu' : state.colormap}`\n                  ],\n                refLevel,\n                colormap: state.colormap,\n                clip: state.clipToBaggervakken,\n                atp: false,\n              })}\n              tileSize={256 * rasterResolutionFactor}\n            >\n              <Layer\n                id={`cog-layer-${id}-${refLevel}`}\n                type=\"raster\"\n                paint={{\n                  'raster-opacity-transition': { duration: 0, delay: 0 },\n                  'raster-opacity': maandLagen.actieveSleutels.has(laagSleutel(id, refLevel))\n                    ? 1\n                    : 0,\n                }}\n                beforeId=\"overlay-separator\"\n              />\n            </Source>\n          ))}\n\n        {/* Reliëf (WP6): een schaduwlaag per maandlaag, met dezelfde zichtbaarheid,\n            zodat de duinen meelopen met de tijdslider. */}\n        {isMapLoaded &&\n          separatorGereed &&\n          state.layers.relief &&\n          toontPeilsoort(state, 'regulier') &&\n          maandLagen.lagen.map(({ id, url, refLevel }) => (\n            <Source\n              key={`relief-${id}`}\n              id={`relief-${id}`}\n              type=\"raster\"\n              url={`cog://${url}#_relief${state.clipToBaggervakken ? '_clip' : ''}`}\n              tileSize={256 * rasterResolutionFactor}\n            >\n              <Layer\n                id={`relief-layer-${id}`}\n                type=\"raster\"\n                paint={{\n                  'raster-opacity-transition': { duration: 0, delay: 0 },\n                  'raster-opacity': maandLagen.actieveSleutels.has(laagSleutel(id, refLevel))\n                    ? 1\n                    : 0,\n                }}\n                beforeId=\"relief-separator\"\n              />\n            </Source>\n          ))}\n\n        {/* ATP COG rasters — rendered above regular COGs via beforeId=overlay-separator.\n            When the bathymetry toggle is off the regulars disappear; ATPs remain\n            visible because they have their own layer toggle. */}\n        {isMapLoaded &&\n          separatorGereed &&\n          toontPeilsoort(state, 'atp') &&\n          atpCogUrls.map(({ id, url, refLevel, tileUrls }) => (\n            <Source\n              key={`atp-${id}-${refLevel}-${state.colormap}`}\n              id={`atp-cog-${id}-${refLevel}`}\n              type=\"raster\"\n              url={cogSourceUrl(url, {\n                refLevel,\n                colormap: state.colormap,\n                clip: state.clipToBaggervakken,\n                atp: true,\n                tileUrl:\n                  tileUrls?.[\n                    `${refLevel}-${refLevel === 'nap' || refLevel === 'lat' ? 'continu' : state.colormap}`\n                  ],\n              })}\n              tileSize={256}\n            >\n              <Layer\n                id={`atp-cog-layer-${id}-${refLevel}`}\n                type=\"raster\"\n                beforeId=\"overlay-separator\"\n              />\n            </Source>\n          ))}\n\n        {/* Baggervak polygons */}\n        {state.layers.baggervakken && baggervakken && (\n          <Source id=\"baggervakken\" type=\"geojson\" data={baggervakken}>\n            <Layer\n              id=\"baggervak-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': 'transparent',\n                'fill-opacity': 0.1,\n              }}\n            />\n            <Layer\n              id=\"baggervak-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.baggervakken,\n                'line-width': 2,\n                'line-opacity': 0.8,\n              }}\n            />\n            <Layer\n              id=\"baggervak-labels\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'code'],\n                'text-size': 12,\n                'text-anchor': 'center',\n                'text-allow-overlap': true,\n              }}\n              paint={{\n                'text-color': LAAG_KLEUR.baggervakken,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n\n        {/* Vaargeul overlay (PolygonZ; Z per vertex = NGD) */}\n        {state.layers.vaargeul && vaargeul && (\n          <Source id=\"vaargeul\" type=\"geojson\" data={vaargeul}>\n            <Layer\n              id=\"vaargeul-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': LAAG_KLEUR.vaargeul,\n                'fill-opacity': 0.12,\n              }}\n            />\n            <Layer\n              id=\"vaargeul-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.vaargeul,\n                'line-width': 1.5,\n                'line-opacity': 0.8,\n              }}\n            />\n          </Source>\n        )}\n\n        {/* Randstroken 5m overlay */}\n        {state.layers.randstroken && randstroken && (\n          <Source id=\"randstroken\" type=\"geojson\" data={randstroken}>\n            <Layer\n              id=\"randstroken-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': LAAG_KLEUR.randstroken,\n                'fill-opacity': 0.2,\n              }}\n            />\n            <Layer\n              id=\"randstroken-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.randstroken,\n                'line-width': 2,\n                'line-dasharray': [4, 3],\n                'line-opacity': 0.8,\n              }}\n            />\n            {/* Geen labels: een randstrook heet in de data \"Baggervak E1\", en met\n                een paar losse oranje labels (MapLibre laat de rest weg) leek\n                juist dat ene vak gemarkeerd. De vakken hebben hun eigen label. */}\n          </Source>\n        )}\n\n        {/* Passeerstroken overlay */}\n        {state.layers.passeerstroken && passeerstroken && (\n          <Source id=\"passeerstroken\" type=\"geojson\" data={passeerstroken}>\n            <Layer\n              id=\"passeerstroken-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.passeerstroken,\n                'line-width': 2,\n                'line-dasharray': [4, 3],\n                'line-opacity': 0.8,\n              }}\n            />\n            <Layer\n              id=\"passeerstroken-labels\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'code'],\n                'text-size': 12,\n                'text-anchor': 'center',\n                'text-allow-overlap': false,\n              }}\n              paint={{\n                'text-color': LAAG_KLEUR.passeerstroken,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1,\n              }}\n            />\n          </Source>\n        )}\n\n        {/* Taluds overlay (PolygonZ; Z varieert per vertex tussen\n            passeerstrook-NGD en vaargeul-NGD — gradient-kleuring volgt in #97) */}\n        {state.layers.taluds && taluds && (\n          <Source id=\"taluds\" type=\"geojson\" data={taluds}>\n            <Layer\n              id=\"taluds-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': LAAG_KLEUR.taluds,\n                'fill-opacity': 0.18,\n              }}\n            />\n            <Layer\n              id=\"taluds-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.taluds,\n                'line-width': 1.5,\n                'line-dasharray': [2, 2],\n                'line-opacity': 0.7,\n              }}\n            />\n          </Source>\n        )}\n\n        {/* Stortlocaties overlay */}\n        {state.layers.stortlocaties && stortlocaties && (\n          <Source id=\"stortlocaties\" type=\"geojson\" data={stortlocaties}>\n            <Layer\n              id=\"stortlocaties-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': LAAG_KLEUR.stortlocaties,\n                'fill-opacity': 0.25,\n              }}\n            />\n            <Layer\n              id=\"stortlocaties-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': LAAG_KLEUR.stortlocaties,\n                'line-width': 2,\n                'line-opacity': 0.8,\n              }}\n            />\n            <Layer\n              id=\"stortlocaties-labels\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'code'],\n                'text-size': 12,\n                'text-anchor': 'center',\n                'text-allow-overlap': false,\n              }}\n              paint={{\n                'text-color': LAAG_KLEUR.stortlocaties,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n\n        {/* ATP bbox + centroid markers — declared LAST so react-map-gl appends\n            them at the top of the layer stack, above all other vector overlays\n            (baggervakken, vaargeul, randstroken, passeerstroken, taluds,\n            stortlocaties). Earlier ordering caused the magenta to be over-painted\n            by those overlays, making ATPs effectively invisible. */}\n        {toontPeilsoort(state, 'atp') && atpBboxGeoJson && (\n          <Source id=\"atp-bbox\" type=\"geojson\" data={atpBboxGeoJson}>\n            <Layer\n              id=\"atp-bbox-fill\"\n              type=\"fill\"\n              paint={{\n                'fill-color': ATP_OUTLINE_COLOR,\n                'fill-opacity': 0.08,\n              }}\n            />\n            <Layer\n              id=\"atp-bbox-glow\"\n              type=\"line\"\n              paint={{\n                'line-color': ATP_OUTLINE_COLOR,\n                'line-width': 16,\n                'line-blur': 10,\n                'line-opacity': 0.6,\n              }}\n            />\n            <Layer\n              id=\"atp-bbox-outline\"\n              type=\"line\"\n              paint={{\n                'line-color': ATP_OUTLINE_COLOR,\n                'line-width': 3,\n                'line-dasharray': [3, 2],\n                'line-opacity': 0.95,\n              }}\n            />\n            <Layer\n              id=\"atp-bbox-label\"\n              type=\"symbol\"\n              layout={{\n                'text-field': 'Aantoonpeiling',\n                'text-size': 12,\n                'symbol-placement': 'line',\n                'text-keep-upright': true,\n                'text-allow-overlap': false,\n              }}\n              paint={{\n                'text-color': ATP_OUTLINE_COLOR,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n        {state.layers.scheepsactiviteit && scheepsactiviteit && (\n          <Source id=\"scheepsactiviteit\" type=\"geojson\" data={scheepsactiviteit}>\n            {/* Twee lagen met dezelfde verf, want `line-dasharray` is in\n                MapLibre geen data-driven eigenschap: het streeppatroon kan niet\n                uit een expressie komen en moet dus zijn eigen laag hebben. De\n                filters sluiten elkaar uit, zodat elk segment precies één keer\n                getekend wordt. */}\n            <Layer\n              id=\"scheepsactiviteit-lijnen\"\n              type=\"line\"\n              filter={['!=', ['get', 'strijdig'], true]}\n              layout={{ 'line-cap': 'round', 'line-join': 'round' }}\n              paint={SCHEEPSACTIVITEIT_VERF}\n            />\n            <Layer\n              id=\"scheepsactiviteit-strijdig\"\n              type=\"line\"\n              filter={['==', ['get', 'strijdig'], true]}\n              layout={{ 'line-cap': 'butt', 'line-join': 'round' }}\n              paint={{ ...SCHEEPSACTIVITEIT_VERF, 'line-dasharray': STRIJDIG_STREEP }}\n            />\n          </Source>\n        )}\n        {/* Dieptecontouren (#114). Vóór de dieptecijfers gedeclareerd, zodat de\n            cijfers erbovenop komen te staan: een lijn door een getal heen leest\n            slechter dan een getal over een lijn heen.\n\n            Hele meters worden dikker getekend en krijgen bij een botsing\n            voorrang, zoals op een topografische kaart. Het label staat op de\n            lijn en herhaalt zich elke 300 pixels, zodat je bij het volgen van\n            een lijn niet hoeft te zoeken bij welke diepte hij hoort. */}\n        {state.layers.dieptecontouren && contourenGeoJson.features.length > 0 && (\n          <Source id=\"dieptecontouren\" type=\"geojson\" data={contourenGeoJson}>\n            <Layer\n              id=\"dieptecontouren-lijn\"\n              type=\"line\"\n              layout={{ 'line-join': 'round', 'line-cap': 'round' }}\n              paint={{\n                'line-color': CONTOUR_KLEUR,\n                'line-width': contourDikteExpressie() as never,\n              }}\n            />\n            <Layer\n              id=\"dieptecontouren-label\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'label'],\n                'text-size': 12,\n                'text-padding': 4,\n                'symbol-placement': 'line',\n                'symbol-spacing': 300,\n                'text-allow-overlap': false,\n                'text-ignore-placement': false,\n                'symbol-sort-key': ['get', 'sortKey'],\n              }}\n              paint={{\n                'text-color': CONTOUR_LABEL_KLEUR,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n        {/* Dieptecijfers (#114). De kaart dunt zelf uit: `text-allow-overlap`\n            uit betekent dat alleen de labels blijven staan die passen, en met\n            `symbol-sort-key` op de omgekeerde waarde wint bij een botsing het\n            ondiepste cijfer. Daardoor kruipt het beeld bij uitzoomen naar de\n            ondieptes toe in plaats van naar een willekeurige greep. Op 12 px\n            (de kleinste maat van de huisstijl) blijven er daardoor bij uitzoomen\n            minder cijfers staan, maar ze overlappen niet. De\n            uitlijning staat op `viewport`, zodat de cijfers noord georiënteerd\n            blijven staan zoals CIV par. 8.1 vraagt, ook in 3D. */}\n        {state.layers.dieptecijfers && dieptecijfersGeoJson.features.length > 0 && (\n          <Source id=\"dieptecijfers\" type=\"geojson\" data={dieptecijfersGeoJson}>\n            <Layer\n              id=\"dieptecijfers-label\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'label'],\n                'text-size': 12,\n                'text-padding': 4,\n                'text-allow-overlap': false,\n                'text-ignore-placement': false,\n                'text-rotation-alignment': 'viewport',\n                'text-pitch-alignment': 'viewport',\n                'symbol-sort-key': ['get', 'sortKey'],\n              }}\n              paint={{\n                'text-color': dieptecijferKleurExpressie() as never,\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n        {/* Ondiepste punt per vak (#115). Net als de ATP-markers bewust als\n            laatste gedeclareerd, zodat de stip boven de vlakken uitkomt en\n            niet wordt overgeschilderd. */}\n        {state.layers.ondiepstePunten && ondiepsteGeoJson.features.length > 0 && (\n          <Source id=\"ondiepste-punten\" type=\"geojson\" data={ondiepsteGeoJson}>\n            <Layer\n              id=\"ondiepste-punten-halo\"\n              type=\"circle\"\n              paint={{\n                'circle-radius': 9,\n                'circle-color': shallowestColorExpression() as never,\n                'circle-opacity': 0.25,\n              }}\n            />\n            <Layer\n              id=\"ondiepste-punten-stip\"\n              type=\"circle\"\n              paint={{\n                'circle-radius': 4,\n                'circle-color': shallowestColorExpression() as never,\n                'circle-stroke-color': '#ffffff',\n                'circle-stroke-width': 1.5,\n              }}\n            />\n            <Layer\n              id=\"ondiepste-punten-label\"\n              type=\"symbol\"\n              layout={{\n                'text-field': ['get', 'label'],\n                'text-size': 12,\n                'text-offset': [0, 1.2],\n                'text-anchor': 'top',\n                'text-allow-overlap': false,\n              }}\n              paint={{\n                'text-color': '#1f2937',\n                'text-halo-color': '#ffffff',\n                'text-halo-width': 1.5,\n              }}\n            />\n          </Source>\n        )}\n        {speld && (\n          // De offset schuift het element een halve stip op: het midden van de\n          // stip staat dan op het punt van de klik. react-map-gl zet het anker\n          // alleen bij het aanmaken, dus een ander anker is een nieuwe marker.\n          <Marker\n            key={speld.anker}\n            longitude={speld.punt.lng}\n            latitude={speld.punt.lat}\n            anchor={speld.anker}\n            offset={speldOffset(speld.anker)}\n          >\n            <DiepteSpeld\n              reading={speldReading}\n              referenceLevel={state.referenceLevel}\n              onSluit={() => setSpeld(null)}\n              anker={speld.anker}\n            />\n          </Marker>\n        )}\n        {/* Cross-section drawing overlay */}\n        <CrossSectionTool mapRef={mapRef} />\n        {/* Meetgebied: tekening en het ondiepste punt erbinnen (#115) */}\n        <GebiedTool mapRef={mapRef} />\n        {/* Schaalbalk en noordpijl, zoals op elke RWS-kaartplot. */}\n        <NavigationControl position=\"bottom-right\" showZoom={false} visualizePitch />\n        <ScaleControl position=\"bottom-right\" unit=\"metric\" maxWidth={120} />\n      </Map>\n    </div>\n  );\n}\n","/** A slot is held until work actually finishes, even when its consumer cancels.\n * Never pause submitted background requests on source readiness: they already\n * occupy MapLibre image-request slots, including slots needed by the foreground.\n * Defer speculative work before mounting sources, not inside the protocol. */\nexport class TileRequestQueue {\n  private active = 0;\n  private backgroundActive = 0;\n  private foreground = new Set<string>();\n  private background = new Set<string>();\n  private foregroundReady = false;\n  private pending: Array<{\n    key: string;\n    start: (background: boolean) => void;\n    cancel: () => void;\n  }> = [];\n\n  constructor(\n    private readonly limit = 8,\n    private readonly backgroundLimit = 2\n  ) {}\n\n  setPlan(\n    foreground: string[],\n    background: string[],\n    options: { foregroundReady: boolean } = { foregroundReady: false }\n  ): void {\n    const previous = new Set([...this.foreground, ...this.background]);\n    this.foreground = new Set(foreground);\n    this.background = new Set(background);\n    this.foregroundReady = options.foregroundReady;\n    for (const task of [...this.pending]) {\n      if (\n        previous.has(task.key) &&\n        !this.foreground.has(task.key) &&\n        !this.background.has(task.key)\n      ) {\n        task.cancel();\n      }\n    }\n    this.drain();\n  }\n\n  schedule<T>(key: string, work: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n    return new Promise((resolve, reject) => {\n      const task = {\n        key,\n        cancel: () => {\n          this.pending = this.pending.filter((p) => p !== task);\n          signal?.removeEventListener('abort', task.cancel);\n          reject(new DOMException('Tile request cancelled', 'AbortError'));\n        },\n        start: (background: boolean) => {\n          signal?.removeEventListener('abort', task.cancel);\n          this.active++;\n          if (background) this.backgroundActive++;\n          // Promise.resolve also catches synchronous failures in work().\n          Promise.resolve()\n            .then(work)\n            .then(resolve, reject)\n            .finally(() => {\n              this.active--;\n              if (background) this.backgroundActive--;\n              this.drain();\n            });\n        },\n      };\n      if (signal?.aborted) {\n        task.cancel();\n        return;\n      }\n      signal?.addEventListener('abort', task.cancel, { once: true });\n      this.pending.push(task);\n      this.drain();\n    });\n  }\n\n  snapshot() {\n    const backgroundQueued = this.pending.filter(\n      (p) => this.background.has(p.key) && !this.foreground.has(p.key)\n    ).length;\n    return {\n      active: this.active,\n      backgroundActive: this.backgroundActive,\n      queued: this.pending.length,\n      foregroundQueued: this.pending.length - backgroundQueued,\n      backgroundQueued,\n      backgroundLimit: this.foregroundReady ? this.limit : this.backgroundLimit,\n    };\n  }\n\n  private drain(): void {\n    while (this.active < this.limit) {\n      // Unknown sources (basemap surroundings/ATP) are foreground too.\n      let index = this.pending.findIndex(\n        (p) => !this.background.has(p.key) || this.foreground.has(p.key)\n      );\n      if (index < 0) {\n        // Use idle capacity to prepare the whole timeline. A new selection\n        // reduces background concurrency but never stops submitted work.\n        if (this.backgroundActive >= (this.foregroundReady ? this.limit : this.backgroundLimit))\n          return;\n        index = 0;\n      }\n      const task = this.pending.splice(index, 1)[0];\n      if (!task) return;\n      task.start(this.background.has(task.key) && !this.foreground.has(task.key));\n    }\n  }\n}\n","import { rasterResolutionFactor } from './kaart-experiment';\n\nimport type { KaartState } from '@/js/pages/kaart/KaartContext';\nimport type { Map } from 'maplibre-gl';\n\n/** Explicitly opt-in, local-only test seam for a repeatable camera position.\n * Date changes still use the real slider. No data/network/renderer is mocked. */\nexport function registreerKaartBenchmark(map: Map, leesState: () => KaartState): () => void {\n  if (\n    !['localhost', '127.0.0.1'].includes(window.location.hostname) ||\n    localStorage.getItem('kaartBenchmark') !== '1'\n  )\n    return () => {};\n  const target = window as unknown as Record<string, unknown>;\n  const canvas = map.getCanvas();\n  const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');\n  const debug = gl?.getExtension('WEBGL_debug_renderer_info');\n  const renderer = gl && debug ? gl.getParameter(debug.UNMASKED_RENDERER_WEBGL) : null;\n  const api = {\n    tileSources() {\n      return Object.keys(map.getStyle().sources)\n        .filter((id) => id.startsWith('cog-'))\n        .map((id) => {\n          const source = map.getSource(id) as any;\n          return {\n            id: Number(id.split('-')[1]),\n            minzoom: source.minzoom,\n            maxzoom: source.maxzoom,\n            bounds: source.bounds,\n          };\n        });\n    },\n    state() {\n      const state = leesState();\n      return {\n        dates: state.availableDates,\n        selectedDate: state.selectedDate,\n        tijdlijn: state.tijdlijn,\n        tilesLoaded: map.areTilesLoaded(),\n      };\n    },\n    setView(center: [number, number], zoom: number) {\n      map.jumpTo({ center, zoom, bearing: 0, pitch: 0 });\n    },\n    snapshot() {\n      const state = leesState();\n      const canvas = map.getCanvas();\n      return {\n        selectedDate: state.selectedDate,\n        dates: state.availableDates,\n        tijdlijn: state.tijdlijn,\n        referenceLevel: state.referenceLevel,\n        retentionMonths: state.retentionMonths,\n        center: map.getCenter().toArray(),\n        zoom: map.getZoom(),\n        rasterResolutionFactor,\n        preparedSourceCount: Object.entries(map.getStyle().sources).filter(\n          ([id, source]) =>\n              id.startsWith('cog-') && /\\/prepared-c5\\/|pngtiles:\\/\\//.test(JSON.stringify(source))\n        ).length,\n        canvas: { width: canvas.width, height: canvas.height },\n        renderer,\n        cogSources: Object.keys(map.getStyle().sources).filter((id) => id.startsWith('cog-')),\n        displayCogSourceCount: Object.entries(map.getStyle().sources).filter(\n          ([id, source]) =>\n            id.startsWith('cog-') &&\n            /display-cog\\.tif|\\/display\\/mm1-v1\\//.test(JSON.stringify(source))\n        ).length,\n      };\n    },\n  };\n  target.__kaartBenchmark = api;\n  return () => {\n    if (target.__kaartBenchmark === api) delete target.__kaartBenchmark;\n  };\n}\n","import { useEffect, useState } from 'react';\n\nimport { getCogUrl } from './use-datasets';\n\nimport type { SurveyDataset } from '@/js/api/types.gen';\nimport type { ReferenceLevel } from '@/js/lib/depth-colors';\nimport type { KaartObjectBronnen } from '@/js/lib/kaart-object';\n\nimport {\n  LEEG_READING,\n  readDepthAtPoint,\n  type DepthReading,\n  type OmgevingLezing,\n} from '@/js/lib/depth-at-point';\n\n/** Waar de speld staat, met de zoom van het moment van de klik. */\nexport interface Speldpunt {\n  lng: number;\n  lat: number;\n  zoom: number;\n}\n\ninterface Opties {\n  punt: Speldpunt | null;\n  datasets: ReadonlyArray<SurveyDataset>;\n  baggervakken: GeoJSON.FeatureCollection | null;\n  referenceLevel: ReferenceLevel;\n  /** Omgevingskaart van deze stap: gelezen buiten een vak of waar de peiling leeg is. */\n  omgeving?: OmgevingLezing | null;\n  /** Stroken, taluds en stortlocaties: een klik daarop hoort bij een vak of heeft een naam. */\n  vormen?: Omit<KaartObjectBronnen, 'baggervakken'>;\n}\n\n/**\n * Diepte op het punt van de speld (ontwerp 2026-10-15, §5).\n *\n * Leest opnieuw zodra de peilingen, de omgeving of het kaartbeeld wisselen: de\n * speld volgt de tijdlijn, dus een andere maand geeft een nieuwe waarde op\n * hetzelfde punt. Tot die binnen is staat er geen waarde, want de oude hoort\n * bij een andere maand en zou zich voordoen als deze.\n */\nexport function useDiepteOpPunt({\n  punt,\n  datasets,\n  baggervakken,\n  referenceLevel,\n  omgeving = null,\n  vormen,\n}: Opties): DepthReading | null {\n  const [reading, setReading] = useState<DepthReading | null>(null);\n\n  useEffect(() => {\n    setReading(null);\n    if (!punt) return;\n    // Een antwoord dat binnenkomt nadat de speld al verzet of de maand al\n    // gewisseld is, is achterhaald.\n    let achterhaald = false;\n    void readDepthAtPoint({\n      lng: punt.lng,\n      lat: punt.lat,\n      datasets,\n      baggervakken,\n      referenceLevel,\n      getCogUrl,\n      zoom: Math.round(punt.zoom),\n      omgeving,\n      vormen,\n    })\n      .then((resultaat) => {\n        if (!achterhaald) setReading(resultaat);\n      })\n      .catch(() => {\n        // Geen waarde is eerlijker dan een speld die blijft zeggen dat hij leest.\n        if (!achterhaald) setReading(LEEG_READING);\n      });\n    return () => {\n      achterhaald = true;\n    };\n  }, [punt, datasets, baggervakken, referenceLevel, omgeving, vormen]);\n\n  return reading;\n}\n","import type { MaandLagenUit } from '@/js/hooks/use-maand-lagen';\n\nexport interface TijdlijnStatus {\n  geladenDatums: string[];\n  ladendeDatums: string[];\n  geselecteerdBezig: boolean;\n  achtergrondBezig: boolean;\n  fout: string | null;\n}\n\nexport function leesTijdlijnStatus(\n  maanden: MaandLagenUit,\n  datum: string | null,\n  bronGeladen: (sleutel: string) => boolean,\n  bronMislukt: (sleutel: string) => boolean,\n  enabled: boolean\n): TijdlijnStatus {\n  if (!enabled || !datum)\n    return {\n      geladenDatums: [],\n      ladendeDatums: [],\n      geselecteerdBezig: false,\n      achtergrondBezig: false,\n      fout: null,\n    };\n  const sleutels = (d: string) => maanden.datumSleutels[d] ?? [];\n  const mislukt = (d: string) => sleutels(d).some(bronMislukt);\n  const geladenDatums = maanden.klareDatums.filter(\n    (d) => !mislukt(d) && sleutels(d).every(bronGeladen)\n  );\n  const ladendeDatums = [\n    ...new Set([\n      ...(maanden.ladendeDatums ?? []),\n      ...maanden.klareDatums.filter((d) => !mislukt(d) && !geladenDatums.includes(d)),\n    ]),\n  ];\n  const fout =\n    maanden.fout || (mislukt(datum) ? 'Peiling kon niet volledig worden geladen.' : null);\n  return {\n    geladenDatums,\n    ladendeDatums,\n    geselecteerdBezig: !fout && !geladenDatums.includes(datum),\n    achtergrondBezig: maanden.bezigMetKlaarzetten || ladendeDatums.some((d) => d !== datum),\n    fout,\n  };\n}\n","import { Slider as SliderPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nfunction Slider({\n  className,\n  defaultValue,\n  value,\n  min = 0,\n  max = 100,\n  ...props\n}: React.ComponentProps<typeof SliderPrimitive.Root>) {\n  const _values = React.useMemo(\n    () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n    [value, defaultValue, min, max]\n  );\n\n  return (\n    <SliderPrimitive.Root\n      data-slot=\"slider\"\n      defaultValue={defaultValue}\n      value={value}\n      min={min}\n      max={max}\n      className={cn(\n        'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',\n        className\n      )}\n      {...props}\n    >\n      <SliderPrimitive.Track\n        data-slot=\"slider-track\"\n        className={cn(\n          'relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5'\n        )}\n      >\n        <SliderPrimitive.Range\n          data-slot=\"slider-range\"\n          className={cn(\n            'absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full'\n          )}\n        />\n      </SliderPrimitive.Track>\n      {Array.from({ length: _values.length }, (_, index) => (\n        <SliderPrimitive.Thumb\n          data-slot=\"slider-thumb\"\n          key={index}\n          className=\"block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n        />\n      ))}\n    </SliderPrimitive.Root>\n  );\n}\n\nexport { Slider };\n","import { ChevronLeft, ChevronRight, Pause, Play } from 'lucide-react';\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\n\nimport { useKaart } from './KaartContext';\n\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/js/components/ui/select';\nimport { Slider } from '@/js/components/ui/slider';\nimport { isVoorspellingsDatum } from '@/js/hooks/use-datasets';\nimport { useSurveyDates } from '@/js/hooks/use-survey-dates';\nimport { NIEUW_LABEL, nieuweBronnenPerMaand } from '@/js/lib/omgeving';\nimport { logTijdlijn } from '@/js/lib/tijdlijn-meting';\nimport { formatMaandKort, formatMaandVoluit, formatRetentionRange } from '@/js/lib/timeline-range';\nimport { cn } from '@/js/lib/utils';\n\nconst PLAY_INTERVAL_MS = 2500;\n\nconst RETENTION_OPTIONS = [\n  { value: '1', label: '1 mnd' },\n  { value: '2', label: '2 mnd' },\n  { value: '3', label: '3 mnd' },\n  { value: '6', label: '6 mnd' },\n  { value: '12', label: '12 mnd' },\n  { value: '0', label: 'Alles' },\n] as const;\n\n/** Plaats van stap i op de tijdlijn (0–1), gelijk met de duimposities van de slider. */\nfunction positie(i: number, aantal: number): number {\n  return aantal > 1 ? i / (aantal - 1) : 0;\n}\n\nexport interface TijdlijnLabel {\n  index: number;\n  tekst: string;\n}\n\n/**\n * Breedte van een label in px, geschat: 12 px Plex Sans is ongeveer 7 px per\n * teken, een jaartal in Plex Mono 7,2.\n */\nconst PX_PER_TEKEN = 7;\nconst TUSSENRUIMTE_PX = 8;\n/** Zolang de balk nog niet gemeten is (eerste render, jsdom): een gangbare breedte. */\nconst STANDAARD_BREEDTE_PX = 600;\n\n/**\n * Labels onder de maandstreepjes, op volgorde van belang: de jaartallen bij\n * januari, de gekozen stap, de verwachtingsstappen, het begin van de tijdlijn\n * zodat je ziet in welk jaar hij begint, en als laatste de jaartallen van de\n * jaren zonder januari (de historische jaarpeilingen). Een label dat over een\n * belangrijker label heen zou vallen, vervalt; bij een lange tijdlijn ligt\n * elke stap maar een paar pixels uit elkaar, dus dat moet op de werkelijke\n * breedte, niet op een aantal stappen.\n */\nexport function tijdlijnLabels(\n  datums: string[],\n  gekozen: number,\n  breedtePx: number\n): TijdlijnLabel[] {\n  const aantal = datums.length;\n  const jaar = (i: number) => datums[i].slice(0, 4);\n  const isJanuari = (i: number) => datums[i].slice(5, 7) === '01';\n  // The first step of a year that has no January step: the historical yearly\n  // surveys (May-August). Index 0 is excluded; it gets its own start label.\n  const isJaarZonderJanuari = (i: number) => i > 0 && !isJanuari(i) && jaar(i) !== jaar(i - 1);\n  const maandJaar = (i: number) => formatRetentionRange(datums[i], 1);\n  const kandidaten: { index: number; teksten: string[] }[] = [];\n  datums.forEach((_, i) => {\n    if (isJanuari(i)) kandidaten.push({ index: i, teksten: [jaar(i)] });\n  });\n  const maand = (i: number) =>\n    // Op de eerste stap hoort het jaar erbij: er staat nog geen jaartal links van.\n    i === 0 ? [maandJaar(0), formatMaandKort(datums[0])] : [formatMaandKort(datums[i])];\n  if (gekozen >= 0 && gekozen < aantal) {\n    // A chosen yearly survey shows its year: with one step per year the month\n    // says little, and the year label of that step would otherwise be lost.\n    const teksten = isJaarZonderJanuari(gekozen)\n      ? [jaar(gekozen), formatMaandKort(datums[gekozen])]\n      : maand(gekozen);\n    kandidaten.push({ index: gekozen, teksten });\n  }\n  datums.forEach((d, i) => {\n    if (isVoorspellingsDatum(d)) kandidaten.push({ index: i, teksten: maand(i) });\n  });\n  if (aantal > 0) kandidaten.push({ index: 0, teksten: [maandJaar(0), jaar(0)] });\n  // Last: these sit one step apart, so on a narrow bar only some of them fit,\n  // and they must never push out the start or the chosen step.\n  datums.forEach((_, i) => {\n    if (isJaarZonderJanuari(i)) kandidaten.push({ index: i, teksten: [jaar(i)] });\n  });\n\n  const bezet: { links: number; rechts: number }[] = [];\n  const labels: TijdlijnLabel[] = [];\n  for (const { index, teksten } of kandidaten) {\n    if (labels.some((l) => l.index === index)) continue;\n    const x = positie(index, aantal) * breedtePx;\n    for (const tekst of teksten) {\n      const w = tekst.length * PX_PER_TEKEN;\n      // Zelfde uitlijning als in de balk: eerste stap links, laatste rechts.\n      const links = index === 0 ? x : index === aantal - 1 ? x - w : x - w / 2;\n      const rechts = links + w;\n      const botst = bezet.some(\n        (b) => links < b.rechts + TUSSENRUIMTE_PX && rechts > b.links - TUSSENRUIMTE_PX\n      );\n      if (botst) continue;\n      bezet.push({ links, rechts });\n      labels.push({ index, tekst });\n      break;\n    }\n  }\n  return labels;\n}\n\nexport default function TimelineBar() {\n  const { state, dispatch } = useKaart();\n  const { dates, laatsteMeting } = useSurveyDates();\n  const [playing, setPlaying] = useState(false);\n  // De labelrij wordt gemeten: of twee labels botsen hangt af van de breedte.\n  const labelRij = useRef<HTMLDivElement>(null);\n  const [labelBreedte, setLabelBreedte] = useState(0);\n\n  // Sync available dates to context\n  useEffect(() => {\n    if (dates.length > 0) {\n      dispatch({ type: 'SET_AVAILABLE_DATES', dates });\n      // Standaard de laatste meting, niet de voorspellingsstap erna.\n      if (!state.selectedDate) {\n        dispatch({ type: 'SET_DATE', date: laatsteMeting ?? dates[dates.length - 1] });\n      }\n    }\n  }, [dates]);\n\n  const currentIndex = state.selectedDate\n    ? state.availableDates.indexOf(state.selectedDate)\n    : state.availableDates.length - 1;\n\n  const goTo = useCallback(\n    (index: number) => {\n      const clamped = Math.max(0, Math.min(index, state.availableDates.length - 1));\n      logTijdlijn('tijdlijn.invoer', { datum: state.availableDates[clamped], index: clamped });\n      dispatch({ type: 'SET_DATE', date: state.availableDates[clamped] });\n    },\n    [state.availableDates, dispatch]\n  );\n\n  // Keep a ref to currentIndex for use in event listeners (avoids stale closures)\n  const indexRef = useRef(currentIndex);\n  indexRef.current = currentIndex;\n\n  // Keyboard navigation: ArrowLeft / ArrowRight step through dates (#45)\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      const target = e.target as HTMLElement;\n      const tag = target.tagName;\n      // Radix handles arrows on its slider thumb. Letting the same event reach\n      // this global shortcut caused a second date step after React had updated.\n      if (\n        e.defaultPrevented ||\n        target.closest?.('[role=\"slider\"]') ||\n        tag === 'INPUT' ||\n        tag === 'TEXTAREA' ||\n        tag === 'SELECT'\n      )\n        return;\n\n      if (e.key === 'ArrowLeft') {\n        e.preventDefault();\n        goTo(indexRef.current - 1);\n      } else if (e.key === 'ArrowRight') {\n        e.preventDefault();\n        goTo(indexRef.current + 1);\n      }\n    };\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [goTo]);\n\n  // Auto-play: step through dates on interval\n\n  useEffect(() => {\n    if (!playing || state.availableDates.length <= 1) return;\n\n    const id = setInterval(() => {\n      const next = indexRef.current + 1 >= state.availableDates.length ? 0 : indexRef.current + 1;\n      logTijdlijn('tijdlijn.afspelen', { datum: state.availableDates[next], index: next });\n      dispatch({ type: 'SET_DATE', date: state.availableDates[next] });\n    }, PLAY_INTERVAL_MS);\n\n    return () => clearInterval(id);\n  }, [playing, state.availableDates, dispatch]);\n\n  // Een sprong in de omgeving komt door iets nieuws dat gezien is, niet door een\n  // gebeurtenis op de plaat; de stip zegt wat (ontwerp 2026-10-14, §1 en §5).\n  const nieuw = useMemo(\n    () => nieuweBronnenPerMaand(state.omgevingKaarten),\n    [state.omgevingKaarten]\n  );\n\n  const heeftDatums = state.availableDates.length > 0;\n  useLayoutEffect(() => {\n    const rij = labelRij.current;\n    if (!rij) return;\n    setLabelBreedte(rij.clientWidth);\n    const waarnemer = new ResizeObserver(() => setLabelBreedte(rij.clientWidth));\n    waarnemer.observe(rij);\n    return () => waarnemer.disconnect();\n  }, [heeftDatums]);\n\n  if (!heeftDatums) return null;\n\n  const datums = state.availableDates;\n  const labelDatum = datums[currentIndex] ?? state.selectedDate;\n  const isVoorspelling = isVoorspellingsDatum(labelDatum);\n  // Eén maand voluit (\"Peilingen van oktober 2026\"); bij Alles is het een\n  // eindmaand, dan hoort er \"t/m\" in de kop en niet \"van t/m\" in de zin.\n  const alles = !isVoorspelling && state.retentionMonths === 0;\n  const kop = isVoorspelling ? 'Verwachting' : alles ? 'Peilingen t/m' : 'Peilingen van';\n  const dateLabel = !labelDatum\n    ? '—'\n    : isVoorspelling || alles || state.retentionMonths === 1\n      ? formatMaandVoluit(labelDatum)\n      : formatRetentionRange(labelDatum, state.retentionMonths);\n  const allesGeladen =\n    !state.tijdlijn.fout && state.tijdlijn.geladenDatums.length === datums.length;\n  const plek = (i: number) => `${positie(i, datums.length) * 100}%`;\n\n  return (\n    // Een vaste balk onder kaart en werkblad in plaats van zwevend over de\n    // kaart: er valt geen geul achter weg en de tijd staat altijd op dezelfde plek.\n    <div className=\"flex h-[76px] shrink-0 items-center gap-4 border-t bg-background px-6 max-md:gap-2 max-md:px-3\">\n      <Button\n        size=\"icon\"\n        className=\"size-[42px] shrink-0 rounded-full\"\n        aria-label={playing ? 'Pauzeren' : 'Afspelen'}\n        onClick={() => setPlaying((p) => !p)}\n        disabled={datums.length <= 1}\n      >\n        {playing ? <Pause className=\"size-[18px]\" /> : <Play className=\"size-[18px]\" />}\n      </Button>\n\n      <div className=\"flex shrink-0 gap-0.5\">\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-8\"\n          aria-label=\"Vorige peiling\"\n          onClick={() => goTo(currentIndex - 1)}\n          disabled={currentIndex <= 0}\n        >\n          <ChevronLeft className=\"size-[18px]\" />\n        </Button>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-8\"\n          aria-label=\"Volgende peiling\"\n          onClick={() => goTo(currentIndex + 1)}\n          disabled={currentIndex >= datums.length - 1}\n        >\n          <ChevronRight className=\"size-[18px]\" />\n        </Button>\n      </div>\n\n      {/* Vaste breedte: een label dat groeit of krimpt verschuift de slider\n          tijdens het slepen. De spatie tussen de twee regels houdt het één\n          zin voor wie het voorgelezen krijgt (\"Verwachting apr 2026\"). */}\n      <div\n        className=\"flex w-[176px] shrink-0 flex-col whitespace-nowrap max-md:w-[120px]\"\n        data-testid=\"tijdlijn-datum\"\n      >\n        <span\n          className={cn(\n            'text-xs',\n            isVoorspelling ? 'font-medium text-verwachting-tekst' : 'text-muted-foreground'\n          )}\n        >\n          {kop}\n        </span>{' '}\n        <span\n          className={cn(\n            'truncate text-base font-semibold tabular-nums',\n            isVoorspelling && 'text-verwachting-tekst'\n          )}\n        >\n          {dateLabel}\n        </span>\n      </div>\n\n      {/* Slider. De kaart volgt het slepen live: sinds het\n          opacity-wisselmechanisme is een datumstap alleen een\n          zichtbaarheidswissel van al gemonteerde lagen, dus tussenstappen\n          kosten geen herlading meer (spike 2026-08-13). */}\n      <div className=\"flex min-w-0 flex-1 flex-col\">\n        {/* mx-2: de duim is 16 px en loopt dus niet van rand tot rand; met\n            dezelfde 8 px inzet staan streepjes, laadbalk en jaartallen precies\n            boven en onder de duimposities. */}\n        <div aria-hidden className=\"relative mx-2 mb-1 h-5\">\n          {datums.map((datum, i) => (\n            <span\n              key={datum}\n              title={datum}\n              className={cn(\n                'absolute bottom-0 -translate-x-1/2',\n                isVoorspellingsDatum(datum)\n                  ? 'size-2.5 rounded-full border-2 border-dashed border-verwachting'\n                  : datum.slice(5, 7) === '01'\n                    ? 'h-3 w-[3px] rounded-sm bg-foreground/70'\n                    : 'h-2 w-[3px] rounded-sm bg-muted-foreground/40'\n              )}\n              style={{ left: plek(i) }}\n            />\n          ))}\n          {datums.map((datum, i) => {\n            const soorten = nieuw[datum.slice(0, 7)];\n            if (!soorten) return null;\n            return (\n              <span\n                key={`nieuw-${datum}`}\n                data-testid=\"stip-nieuw\"\n                title={soorten.map((s) => NIEUW_LABEL[s]).join(', ')}\n                // Recht boven het streepje van zijn maand: ernaast leek hij bij\n                // geen enkele maand te horen (Jelle, 15-10). Leisteen, geen oranje:\n                // oranje is in de bediening \"verwachting\", en dit is juist iets\n                // dat gezien is.\n                className=\"absolute top-0 size-1.5 -translate-x-1/2 rounded-full bg-leisteen\"\n                style={{ left: plek(i) }}\n              />\n            );\n          })}\n        </div>\n        {/* De gekozen maand als gevulde blauwe stip met het gekozen-vlak eromheen,\n            zoals de legenda ernaast hem toont. */}\n        <Slider\n          aria-label=\"Peildatum\"\n          className=\"[&_[data-slot=slider-thumb]]:bg-primary [&_[data-slot=slider-thumb]]:ring-4 [&_[data-slot=slider-thumb]]:ring-gekozen [&_[data-slot=slider-thumb]]:focus-visible:ring-ring/50\"\n          value={[currentIndex]}\n          min={0}\n          max={Math.max(0, datums.length - 1)}\n          step={1}\n          onValueChange={([v]) => goTo(v)}\n          onValueCommit={([v]) => goTo(v)}\n        />\n        {/* Laadstatus als dunne balk, per stap een stukje: gevuld = de stap\n            ernaartoe is instant, vaag = moet nog laden, kloppend = wordt nu\n            geladen. Als alles klaarstaat verdwijnt hij uit beeld, maar de\n            stukjes blijven in de boom: de tijdlijnmeting telt ze. */}\n        <div\n          className={cn(\n            'relative mx-2 mt-1.5 h-[3px] transition-opacity duration-500',\n            allesGeladen && 'opacity-0'\n          )}\n          data-testid=\"tijdlijn-stipjes\"\n        >\n          {datums.map((datum, i) => {\n            const geladen = state.tijdlijn.geladenDatums.includes(datum);\n            const laadtNu = state.tijdlijn.ladendeDatums.includes(datum);\n            const halveStap = datums.length > 1 ? 0.5 / (datums.length - 1) : 0.5;\n            const begin = Math.max(0, positie(i, datums.length) - halveStap);\n            const eind = Math.min(1, positie(i, datums.length) + halveStap);\n            return (\n              <span\n                key={datum}\n                data-testid={geladen ? 'stip-geladen' : 'stip-ongeladen'}\n                title={datum}\n                className={cn(\n                  'absolute inset-y-0 overflow-hidden border-x border-background transition-colors',\n                  geladen ? 'bg-primary/60' : 'bg-muted-foreground/20'\n                )}\n                style={{ left: `${begin * 100}%`, width: `${(eind - begin) * 100}%` }}\n              >\n                {laadtNu && (\n                  <span\n                    data-testid=\"stip-laadt\"\n                    className=\"absolute inset-0 animate-pulse bg-primary\"\n                  />\n                )}\n              </span>\n            );\n          })}\n        </div>\n        <div\n          ref={labelRij}\n          aria-hidden\n          className=\"relative mx-2 mt-1 h-4 text-xs text-muted-foreground\"\n        >\n          {tijdlijnLabels(datums, currentIndex, labelBreedte || STANDAARD_BREEDTE_PX).map(\n            ({ index, tekst }) => (\n              <span\n                key={index}\n                // Eerste en laatste stap staan op de rand: gecentreerd vielen ze\n                // half buiten de balk.\n                className={cn(\n                  'absolute top-0 whitespace-nowrap',\n                  /^\\d{4}$/.test(tekst) ? 'getal' : 'tabular-nums',\n                  index === datums.length - 1 && index > 0\n                    ? '-translate-x-full'\n                    : index > 0 && '-translate-x-1/2',\n                  isVoorspellingsDatum(datums[index])\n                    ? 'text-verwachting-tekst'\n                    : index === currentIndex && 'text-primary',\n                  index === currentIndex && 'font-medium'\n                )}\n                style={{ left: plek(index) }}\n              >\n                {tekst}\n              </span>\n            )\n          )}\n        </div>\n      </div>\n\n      {/* Alleen tekst zolang er iets te melden is: als alles klaarstaat, is een\n          teller \"gereed\" alleen maar iets om overheen te lezen. */}\n      <span\n        role=\"status\"\n        className={cn(\n          'w-[150px] shrink-0 text-xs leading-snug max-lg:w-24',\n          state.tijdlijn.fout ? 'text-destructive' : 'text-muted-foreground'\n        )}\n        data-testid=\"tijdlijn-status\"\n      >\n        {state.tijdlijn.fout\n          ? `${state.tijdlijn.fout} Vernieuw de kaart als dit aanhoudt.`\n          : state.tijdlijn.geselecteerdBezig\n            ? 'Gekozen maand laden…'\n            : state.tijdlijn.achtergrondBezig\n              ? `Tijdlijn voorbereiden (${state.tijdlijn.geladenDatums.length}/${datums.length})…`\n              : ''}\n      </span>\n\n      <div className=\"flex shrink-0 flex-col gap-0.5 text-xs text-muted-foreground max-xl:hidden\">\n        <span className=\"flex items-center gap-1.5\">\n          <span className=\"size-2.5 rounded-full bg-primary ring-2 ring-gekozen\" />\n          Peiling\n        </span>\n        <span className=\"flex items-center gap-1.5\">\n          <span className=\"size-2.5 rounded-full border-2 border-dashed border-verwachting\" />\n          Verwachting\n        </span>\n        <span className=\"flex items-center gap-1.5\">\n          <span className=\"flex size-2.5 items-center justify-center\">\n            <span className=\"size-1.5 rounded-full bg-leisteen\" />\n          </span>\n          Nieuwe foto of loding\n        </span>\n      </div>\n\n      <div className=\"flex shrink-0 items-center gap-2 text-xs text-muted-foreground\">\n        <span className=\"max-md:sr-only\">Venster</span>\n        <Select\n          value={String(state.retentionMonths)}\n          onValueChange={(v) => dispatch({ type: 'SET_RETENTION_MONTHS', months: Number(v) })}\n        >\n          <SelectTrigger aria-label=\"Venster\" className=\"h-8 w-[92px] text-[13px] text-foreground\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            {RETENTION_OPTIONS.map((opt) => (\n              <SelectItem key={opt.value} value={opt.value} className=\"text-[13px]\">\n                {opt.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n      </div>\n    </div>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport { apiDatasetsSurveyDatesRetrieve } from '@/js/api';\nimport { setVoorspellingsDatums } from '@/js/hooks/use-datasets';\n\n/**\n * Fetch sorted unique month-start dates for the timeline.\n *\n * Uses the dedicated `/api/datasets/survey-dates/` endpoint — a tiny payload\n * instead of listing up to 1000 full dataset records (incl. heavy JSON\n * fields) only to derive the month stops client-side.\n */\nexport function useSurveyDates() {\n  const [dates, setDates] = useState<string[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [laatsteMeting, setLaatsteMeting] = useState<string | null>(null);\n\n  useEffect(() => {\n    let cancelled = false;\n\n    (async () => {\n      try {\n        const { data } = await apiDatasetsSurveyDatesRetrieve();\n        if (cancelled) return;\n        // De voorspellingsmaanden staan al in `dates`; de laatste maand die\n        // géén voorspellingsmaand is, is de laatste meetronde.\n        const voorspellingen = data?.voorspellingen ?? [];\n        const alle = data?.dates ?? [];\n        setVoorspellingsDatums(voorspellingen);\n        setLaatsteMeting(alle.filter((d) => !voorspellingen.includes(d)).at(-1) ?? null);\n        setDates(alle);\n      } catch {\n        if (!cancelled) setDates([]);\n      } finally {\n        if (!cancelled) setLoading(false);\n      }\n    })();\n\n    return () => {\n      cancelled = true;\n    };\n  }, []);\n\n  return { dates, loading, laatsteMeting };\n}\n","import { Hexagon, Pencil, X } from 'lucide-react';\nimport { useEffect, useMemo } from 'react';\n\nimport { useKaart } from './KaartContext';\n\nimport type { SurveyDataset } from '@/js/api';\n\nimport { apiDatasetsGebiedStatistiekCreate } from '@/js/api';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  DEKKING_DREMPEL,\n  dekking,\n  formatCmBovenNgd,\n  formatDiepte,\n  formatKubering,\n  formatOppervlak,\n} from '@/js/lib/gebied';\nimport { cn } from '@/js/lib/utils';\nimport { cmBadgeKlasse } from '@/js/pages/analyse/VakOverviewTable';\n\n/**\n * Rekent het getekende meetgebied door (#115, WP3 taak 2).\n *\n * Staat los van het tabblad, omdat de uitkomst ook op de kaart staat (het\n * ondiepste punt als marker): die moet meelopen met een datumwissel, ook als\n * het werkblad op een ander tabblad staat of ingeklapt is.\n *\n * Er wordt uitsluitend op reguliere peilingen gemeten. Een aantoonpeiling dekt\n * maar een deel van een vak; die in hetzelfde gemiddelde gooien zou twee\n * soorten metingen over hetzelfde stuk bodem door elkaar halen zonder dat het\n * getal laat zien dat dat gebeurd is.\n */\nexport function useGebiedStatistiek(datasets: SurveyDataset[]) {\n  const { state, dispatch } = useKaart();\n  const { ring } = state.gebied;\n\n  // De hook levert bij elke render een nieuwe array, dus de identiteit ervan\n  // is geen bruikbare dependency. De ids als string zijn dat wel.\n  const idsSleutel = datasets.map((ds) => ds.id).join(',');\n  const datasetIds = useMemo(\n    () => (idsSleutel ? idsSleutel.split(',').map(Number) : []),\n    [idsSleutel]\n  );\n\n  // Opnieuw rekenen zodra het gebied verandert of er een andere peiling op de\n  // kaart komt te liggen. Zonder dat laatste zou het tabblad na een datumwissel\n  // een getal blijven tonen dat bij de vorige peiling hoort.\n  useEffect(() => {\n    if (!ring || ring.length < 3 || datasetIds.length === 0) return;\n\n    let afgebroken = false;\n    dispatch({ type: 'GEBIED_BEZIG' });\n\n    (async () => {\n      try {\n        const response = await apiDatasetsGebiedStatistiekCreate({\n          body: { ring, dataset_ids: datasetIds },\n        });\n        if (afgebroken) return;\n        if (response.data) {\n          dispatch({ type: 'GEBIED_RESULTAAT', resultaat: response.data });\n        } else {\n          dispatch({ type: 'GEBIED_FOUT', fout: 'De berekening leverde geen antwoord op.' });\n        }\n      } catch {\n        if (!afgebroken) {\n          dispatch({ type: 'GEBIED_FOUT', fout: 'De berekening is niet gelukt.' });\n        }\n      }\n    })();\n\n    return () => {\n      afgebroken = true;\n    };\n  }, [ring, datasetIds, dispatch]);\n}\n\n/**\n * Tabblad Gebied: de uitkomst van een zelfgetekend meetgebied.\n *\n * Binnen de Eemsgeul wordt op de ondiepste waarde gestuurd, dus die staat\n * groot en met zijn afstand tot de NGD erbij. De gemiddelde diepte staat er\n * even groot naast omdat dat de tweede helft van Wiebes vraag is.\n */\nexport default function WerkbladGebied() {\n  const { state, dispatch } = useKaart();\n  const { drawing, ring, bezig, resultaat, fout } = state.gebied;\n\n  if (!ring) {\n    return (\n      <div className=\"flex flex-col items-start gap-3 px-5 py-6\">\n        <Hexagon className=\"size-7 text-muted-foreground/70\" strokeWidth={1.5} />\n        <h3 className=\"text-[15px] font-semibold\">Gebied meten</h3>\n        <p className=\"text-[13px] leading-normal text-muted-foreground\">\n          Klik de hoekpunten op de kaart en dubbelklik om te sluiten. Gemiddelde en ondiepste diepte\n          en de kuubs per vak komen hier te staan.\n        </p>\n        {drawing ? (\n          <Button\n            variant=\"outline\"\n            className=\"h-[38px]\"\n            onClick={() => dispatch({ type: 'CANCEL_GEBIED' })}\n          >\n            Tekenen stoppen (Esc)\n          </Button>\n        ) : (\n          <Button className=\"h-[38px]\" onClick={() => dispatch({ type: 'START_GEBIED' })}>\n            Begin met tekenen\n          </Button>\n        )}\n      </div>\n    );\n  }\n\n  const gemetenDekking = resultaat ? dekking(resultaat.area_m2, resultaat.polygon_area_m2) : null;\n\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"flex items-center justify-between border-b py-2 pr-3 pl-5\">\n        <div className=\"flex items-center gap-2\">\n          <h3 className=\"text-[15px] font-semibold\">Gebied</h3>\n          {bezig && <span className=\"text-xs text-muted-foreground\">Rekenen…</span>}\n        </div>\n        <div className=\"flex items-center gap-0.5\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-8\"\n            title=\"Opnieuw tekenen\"\n            aria-label=\"Opnieuw tekenen\"\n            onClick={() => dispatch({ type: 'START_GEBIED' })}\n          >\n            <Pencil className=\"size-4\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-8\"\n            title=\"Gebied wissen\"\n            aria-label=\"Gebied wissen\"\n            onClick={() => dispatch({ type: 'CANCEL_GEBIED' })}\n          >\n            <X className=\"size-4\" />\n          </Button>\n        </div>\n      </div>\n\n      <div className=\"space-y-3 px-5 py-4\">\n        {fout && <p className=\"text-xs text-destructive\">{fout}</p>}\n\n        {!fout && resultaat && resultaat.per_vak.length === 0 && (\n          <p className=\"text-[13px] text-muted-foreground\">\n            Dit gebied valt buiten de peilingen die nu op de kaart staan.\n          </p>\n        )}\n\n        {!fout && resultaat && resultaat.per_vak.length > 0 && (\n          <>\n            <div className=\"grid grid-cols-2 gap-3\">\n              <Kerngetal\n                label=\"Gemiddelde diepte (m NAP)\"\n                waarde={formatDiepte(resultaat.depth_mean)}\n              />\n              <Kerngetal\n                label=\"Ondiepste (m NAP)\"\n                waarde={formatDiepte(resultaat.depth_max)}\n                bijschrift={\n                  resultaat.cm_above_ngd != null ? (\n                    // Dezelfde badge als in de vaktabel, op het hele getal dat hier\n                    // staat: \"0 cm\" in het rood zou zichzelf tegenspreken.\n                    <span\n                      className={cn(\n                        'getal rounded-md px-1.5 py-0.5',\n                        cmBadgeKlasse(Math.round(resultaat.cm_above_ngd), false)\n                      )}\n                    >\n                      {formatCmBovenNgd(resultaat.cm_above_ngd)} t.o.v. NGD\n                    </span>\n                  ) : null\n                }\n              />\n            </div>\n\n            <dl className=\"space-y-1 text-[13px]\">\n              <Regel label=\"Diepste\" waarde={formatDiepte(resultaat.depth_min)} />\n              <Regel label=\"Gemeten oppervlak\" waarde={formatOppervlak(resultaat.area_m2)} />\n              <Regel\n                label=\"Boven NGD\"\n                waarde={`${formatKubering(resultaat.volume_above_ngd_m3, 'm³')} over ${formatOppervlak(\n                  resultaat.surface_above_ngd_m2\n                )}`}\n              />\n            </dl>\n\n            {gemetenDekking != null && gemetenDekking < DEKKING_DREMPEL && (\n              <p className=\"text-xs text-muted-foreground\">\n                {Math.round(gemetenDekking * 100)}% van het getekende gebied is gepeild. De cijfers\n                gaan alleen over dat deel.\n              </p>\n            )}\n\n            {resultaat.per_vak.length > 1 && (\n              <table className=\"w-full text-xs\">\n                <thead className=\"text-muted-foreground\">\n                  <tr>\n                    <th className=\"pb-1 text-left font-medium\">Vak</th>\n                    <th className=\"pb-1 text-right font-medium\">Gem.</th>\n                    <th className=\"pb-1 text-right font-medium\">Ondiepst</th>\n                    <th className=\"pb-1 text-right font-medium\">Opp.</th>\n                  </tr>\n                </thead>\n                <tbody>\n                  {resultaat.per_vak.map((rij) => (\n                    <tr key={rij.dataset_id} className=\"border-t\">\n                      <td className=\"py-1 font-medium\">{rij.baggervak}</td>\n                      <td className=\"py-1 text-right getal\">{formatDiepte(rij.depth_mean)}</td>\n                      <td className=\"py-1 text-right getal\">{formatDiepte(rij.depth_max)}</td>\n                      <td className=\"py-1 text-right getal\">{formatOppervlak(rij.area_m2)}</td>\n                    </tr>\n                  ))}\n                </tbody>\n              </table>\n            )}\n          </>\n        )}\n\n        {!fout && !resultaat && !bezig && (\n          <p className=\"text-[13px] text-muted-foreground\">\n            Geen peilingen zichtbaar om op te meten.\n          </p>\n        )}\n      </div>\n    </div>\n  );\n}\n\nfunction Kerngetal({\n  label,\n  waarde,\n  bijschrift,\n}: {\n  label: string;\n  waarde: string;\n  bijschrift?: React.ReactNode;\n}) {\n  return (\n    <div>\n      <p className=\"text-xs text-muted-foreground\">{label}</p>\n      <p className=\"getal text-lg font-medium\">{waarde}</p>\n      {bijschrift && <p className=\"mt-0.5 text-xs\">{bijschrift}</p>}\n    </div>\n  );\n}\n\nfunction Regel({ label, waarde }: { label: string; waarde: string }) {\n  return (\n    <div className=\"flex justify-between gap-2\">\n      <dt className=\"text-muted-foreground\">{label}</dt>\n      <dd className=\"getal\">{waarde}</dd>\n    </div>\n  );\n}\n","/**\n * Waar de keuze heen gaat bij een pijltoets in een rij tabs of keuzerondjes.\n *\n * De rol (tab, radio) belooft een schermlezer dat ← → werkt; zonder dit drukt\n * die gebruiker op een pijl en gebeurt er niets. Alleen het gekozen item staat\n * in de tabvolgorde, de pijlen lopen de rest langs en gaan rond aan de randen.\n *\n * Geeft `null` voor een toets die niets met de rij te maken heeft, zodat die\n * gewoon zijn eigen werk doet.\n */\nexport function volgendeIndex(toets: string, huidig: number, aantal: number): number | null {\n  if (aantal <= 0) return null;\n  switch (toets) {\n    case 'ArrowRight':\n    case 'ArrowDown':\n      return (huidig + 1 + aantal) % aantal;\n    case 'ArrowLeft':\n    case 'ArrowUp':\n      return (huidig - 1 + aantal) % aantal;\n    case 'Home':\n      return 0;\n    case 'End':\n      return aantal - 1;\n    default:\n      return null;\n  }\n}\n","import { ChevronsRight } from 'lucide-react';\n\nimport { useKaart, toontPeilsoort } from './KaartContext';\n\nimport type { KaartReferentie, ReferenceLevel } from '@/js/lib/depth-colors';\n\nimport { Button } from '@/js/components/ui/button';\nimport { isVoorspellingsDatum } from '@/js/hooks/use-datasets';\nimport {\n  DIFFERENCE_BINS,\n  KANS_BINS,\n  getLegendStops,\n  heeftColormapKeuze,\n} from '@/js/lib/depth-colors';\nimport {\n  CONTOUR_GENERALISATIE_M,\n  CONTOUR_INTERVAL_M,\n  CONTOUR_KLEUR,\n  heeftContouren,\n} from '@/js/lib/dieptecontouren';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { legendaTekst, omgevingVoorDatum, variantVoor } from '@/js/lib/omgeving';\nimport { VERANDERING_STOPS, veranderingPositie } from '@/js/lib/palet-verandering';\nimport { ATP_OUTLINE_COLOR, PEILSOORT_LABELS } from '@/js/lib/peilsoort';\nimport { volgendeIndex } from '@/js/lib/pijltoetsen';\nimport { cn } from '@/js/lib/utils';\n\nexport type Peil = 'nap' | 'lat';\n\n/**\n * Korte naam van wat de kaart toont, voor de ingeklapte strook en de knoppen.\n *\n * In het bestek 2026 is NGD de minimale en OHD de maximale waterbodemdiepte;\n * de interface zegt daarom \"max.\" in plaats van OHD. IVD komt er niet meer in\n * voor en staat hier dus ook niet.\n */\nexport const REFERENTIE_NAAM: Record<KaartReferentie, string> = {\n  ngd: 'NGD',\n  ohd: 'max.',\n  nap: 'NAP',\n  lat: 'LAT',\n  verschil: 'Verschil',\n};\n\n/** Hetzelfde, maar zoals het in een zin staat: \"boven de maximale diepte\". */\nexport const REFERENTIE_ZIN: Record<KaartReferentie, string> = {\n  ngd: 'NGD',\n  ohd: 'de maximale diepte',\n  nap: 'NAP',\n  lat: 'LAT',\n  verschil: 'de vorige peiling',\n};\n\n// Wat elk kaartbeeld betekent. Diepte rekent vanaf een peil (NAP of LAT) en\n// krijgt de doorlopende dieptekleuren; NGD en max. zijn ontwerpdieptes en\n// krijgen de verschilkleuren (#114). Verschil zet de peiling af tegen de vorige\n// van hetzelfde vak (bestek 2026, art. 32.3.2 lid 2).\nconst BEELDEN = [\n  { beeld: 'diepte', kort: 'Diepte' },\n  { beeld: 'ngd', kort: 'NGD', sub: 'minimale diepte' },\n  { beeld: 'ohd', kort: 'Max.', sub: 'maximale diepte' },\n  { beeld: 'verschil', kort: 'Verschil', sub: 'vorige peiling' },\n] as const;\n\ntype Beeld = (typeof BEELDEN)[number]['beeld'];\n\n/** Welke knop bij de referentie hoort: een peil valt onder Diepte. */\nfunction beeldVan(referentie: KaartReferentie): Beeld {\n  return referentie === 'nap' || referentie === 'lat' ? 'diepte' : referentie;\n}\n\nconst COLORMAPS = [\n  ['discreet', 'Klassen'],\n  ['continu', 'Verloop'],\n  ['kans', 'Kans'],\n] as const;\n\n/** Een streepje onder de legendabalk: waar het staat en wat erbij hoort. */\ninterface Streep {\n  positie: number;\n  label: string;\n}\n\n/**\n * De legendabalk als CSS-verloop, plus de waarden aan beide uiteinden.\n *\n * Eén functie voor de liggende legenda in het werkblad en de staande in de\n * ingeklapte strook: het blijft dezelfde schaal, alleen een kwartslag gedraaid.\n * Links (of onder) staat altijd het diepe of onwaarschijnlijke uiteinde.\n */\nexport function legendaBalk(\n  referentie: ReferenceLevel,\n  colormap: 'continu' | 'discreet' | 'kans',\n  richting: 'to right' | 'to top'\n): { achtergrond: string; begin: string; eind: string } {\n  if (referentie === 'verschil') {\n    const stops = VERANDERING_STOPS.map(\n      (s) => `${s.color} ${veranderingPositie(s.meter).toFixed(1)}%`\n    );\n    return {\n      achtergrond: `linear-gradient(${richting}, ${stops.join(', ')})`,\n      begin: getal(-1),\n      eind: getal(1, 0, { plusteken: true }),\n    };\n  }\n  if (!heeftColormapKeuze(referentie)) {\n    const stops = getLegendStops(undefined, 19);\n    return {\n      achtergrond: `linear-gradient(${richting}, ${stops.map((s) => s.color).join(', ')})`,\n      begin: getal(stops[0].depth),\n      eind: getal(stops[stops.length - 1].depth, 0, { plusteken: true }),\n    };\n  }\n  if (colormap === 'kans') {\n    return {\n      achtergrond: harderVerloop(\n        KANS_BINS.slice()\n          .reverse()\n          .map((b) => b.color),\n        richting\n      ),\n      // Dezelfde notatie als de vakjes van de liggende legenda.\n      begin: KANS_BINS[KANS_BINS.length - 1].label,\n      eind: KANS_BINS[0].label,\n    };\n  }\n  const bins = DIFFERENCE_BINS.slice().reverse();\n  return {\n    achtergrond:\n      colormap === 'discreet'\n        ? harderVerloop(\n            bins.map((b) => b.color),\n            richting\n          )\n        : `linear-gradient(${richting}, ${bins\n            .map((b) => `${b.color} ${verschilPositie(b.midden).toFixed(1)}%`)\n            .join(', ')})`,\n    begin: getal(-2),\n    eind: getal(2, 0, { plusteken: true }),\n  };\n}\n\n/** Even brede vakken met harde grenzen: klassen zijn geen verloop. */\nfunction harderVerloop(kleuren: string[], richting: string): string {\n  const n = kleuren.length;\n  const stops = kleuren.flatMap((kleur, i) => [\n    `${kleur} ${((i * 100) / n).toFixed(2)}%`,\n    `${kleur} ${(((i + 1) * 100) / n).toFixed(2)}%`,\n  ]);\n  return `linear-gradient(${richting}, ${stops.join(', ')})`;\n}\n\n/** Waar een verschil in de verloopbalk ligt, van −2,5 m links tot +2,5 m rechts. */\nfunction verschilPositie(meter: number): number {\n  return ((meter + 2.5) / 5) * 100;\n}\n\nfunction dieptestrepen(): Streep[] {\n  // Stops elke meter van −16 tot +2; elke derde krijgt een getal. Dat geeft\n  // −16, −13, … +2: gelijk verdeeld, dus elk getal staat bij zijn eigen kleur.\n  const stops = getLegendStops(undefined, 19);\n  return stops\n    .map((s, i) => ({ positie: (i / (stops.length - 1)) * 100, depth: s.depth, i }))\n    .filter((s) => s.i % 3 === 0)\n    .map((s) => ({ positie: s.positie, label: getal(s.depth, 0, { plusteken: true }) }));\n}\n\nfunction verloopstrepen(): Streep[] {\n  return [-2, -1, 0, 1, 2].map((m) => ({\n    positie: verschilPositie(m),\n    label: `${getal(m, 0, { plusteken: true })} m`,\n  }));\n}\n\nfunction veranderingstrepen(): Streep[] {\n  return [-1, -0.5, 0, 0.5, 1].map((m) => ({\n    positie: veranderingPositie(m),\n    label: `${getal(m, Number.isInteger(m) ? 0 : 1, { plusteken: true })} m`,\n  }));\n}\n\nfunction klassestrepen(): Streep[] {\n  // Op de grenzen tussen de klassen, niet op elke: twaalf getallen passen niet\n  // naast elkaar. De klasse zelf staat in de titel van elk vakje.\n  const n = DIFFERENCE_BINS.length;\n  return [\n    [1, -2],\n    [3, -0.5],\n    [6, 0],\n    [9, 0.5],\n    [11, 2],\n  ].map(([grens, m]) => ({\n    positie: (grens / n) * 100,\n    label: `${getal(m, 1, { plusteken: true }).replace(/,0$/, '')} m`,\n  }));\n}\n\nfunction Strepen({ strepen }: { strepen: Streep[] }) {\n  return (\n    <div className=\"relative h-4 text-xs getal text-muted-foreground\">\n      {strepen.map((s) => (\n        <span\n          key={s.label}\n          className={cn(\n            'absolute top-0 whitespace-nowrap',\n            // De buitenste getallen schuiven naar binnen, anders vallen ze\n            // half buiten het werkblad.\n            s.positie <= 0 ? 'left-0' : s.positie >= 100 ? 'right-0' : '-translate-x-1/2'\n          )}\n          style={s.positie > 0 && s.positie < 100 ? { left: `${s.positie}%` } : undefined}\n        >\n          {s.label}\n        </span>\n      ))}\n    </div>\n  );\n}\n\n/** Even brede vakjes, elk met zijn klasse als naam: de klassen- en de kanslegenda. */\nfunction Vakjes({ bins }: { bins: { color: string; label: string }[] }) {\n  return (\n    <div className=\"flex h-3 overflow-hidden rounded-[3px]\">\n      {bins.map((bin) => {\n        const label = bin.label.replace(/-/g, '−');\n        return (\n          <span\n            key={bin.label}\n            role=\"img\"\n            aria-label={label}\n            title={label}\n            className=\"flex-1\"\n            style={{ backgroundColor: bin.color }}\n          />\n        );\n      })}\n    </div>\n  );\n}\n\nfunction Legenda() {\n  const { state } = useKaart();\n  const isPeil = !heeftColormapKeuze(state.referenceLevel);\n  const naam = REFERENTIE_NAAM[state.referenceLevel];\n  const zin = REFERENTIE_ZIN[state.referenceLevel];\n\n  if (state.referenceLevel === 'verschil') {\n    return (\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs font-medium text-muted-foreground\">\n          Verschil t.o.v. de vorige peiling, in m\n        </span>\n        <div\n          className=\"h-3 rounded-[3px]\"\n          style={{ background: legendaBalk('verschil', state.colormap, 'to right').achtergrond }}\n        />\n        <Strepen strepen={veranderingstrepen()} />\n      </div>\n    );\n  }\n\n  if (isPeil) {\n    return (\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs font-medium text-muted-foreground\">Bodemhoogte in m {naam}</span>\n        <div\n          className=\"h-3 rounded-[3px]\"\n          style={{\n            background: legendaBalk(state.referenceLevel, state.colormap, 'to right').achtergrond,\n          }}\n        />\n        <Strepen strepen={dieptestrepen()} />\n      </div>\n    );\n  }\n\n  if (state.colormap === 'kans') {\n    const bins = KANS_BINS.slice().reverse();\n    return (\n      <div className=\"flex flex-col gap-1\">\n        <span className=\"text-xs font-medium text-muted-foreground\">\n          Kans boven {zin} bij de volgende peiling\n        </span>\n        <Vakjes bins={bins} />\n        <div className=\"flex text-xs getal text-muted-foreground\">\n          {bins.map((bin) => (\n            <span key={bin.label} className=\"flex-1 text-center whitespace-nowrap\">\n              {bin.label}\n            </span>\n          ))}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <span className=\"text-xs font-medium text-muted-foreground\">Verschil t.o.v. {zin}</span>\n      {state.colormap === 'discreet' ? (\n        <Vakjes bins={DIFFERENCE_BINS.slice().reverse()} />\n      ) : (\n        <div\n          className=\"h-3 rounded-[3px]\"\n          style={{\n            background: legendaBalk(state.referenceLevel, 'continu', 'to right').achtergrond,\n          }}\n        />\n      )}\n      <Strepen strepen={state.colormap === 'discreet' ? klassestrepen() : verloopstrepen()} />\n    </div>\n  );\n}\n\n/**\n * De regel naast de keuze. Bij de kans in oranje en als vaste tekst, niet als\n * tooltip: dit beeld mag nooit voor een meting worden aangezien. Hetzelfde\n * principe als in het Vooruitblik-ontwerp van 14 september.\n */\nfunction Uitleg() {\n  const { state } = useKaart();\n  const zin = REFERENTIE_ZIN[state.referenceLevel];\n\n  if (state.referenceLevel === 'verschil') {\n    return (\n      <span className=\"text-xs leading-snug text-muted-foreground\">\n        Blauw is verdiept, rood is opgehoogd t.o.v. de vorige peiling. Een vak zonder eerdere\n        peiling blijft leeg.\n      </span>\n    );\n  }\n  if (!heeftColormapKeuze(state.referenceLevel)) {\n    return <span className=\"text-xs leading-snug text-muted-foreground\">Geel is ondiep.</span>;\n  }\n  if (state.colormap !== 'kans' && state.referenceLevel === 'ohd') {\n    // De maximale diepte mag niet overschreden worden: dieper is de\n    // overschrijding, erboven is toegestaan zolang het onder NGD blijft.\n    return (\n      <span className=\"text-xs leading-snug text-muted-foreground\">\n        Groen tot blauw: de bodem ligt dieper dan de maximale diepte, dat is te diep. Geel tot rood:\n        de bodem ligt erboven; tot aan NGD is dat in orde.\n      </span>\n    );\n  }\n  if (state.colormap !== 'kans') {\n    return (\n      <span className=\"text-xs leading-snug text-muted-foreground\">\n        Geel tot rood: de bodem ligt boven {zin}.\n      </span>\n    );\n  }\n\n  const maand = state.selectedDate\n    ? new Date(`${state.selectedDate}T00:00:00`).toLocaleDateString('nl-NL', {\n        month: 'long',\n        year: 'numeric',\n      })\n    : null;\n  return (\n    <span role=\"note\" className=\"text-xs leading-snug text-verwachting-tekst\">\n      <span className=\"font-medium\">Verwachting, geen meting.</span> Kans dat de bodem bij de\n      volgende peiling boven {zin} ligt\n      {maand ? `, uitgaande van de peilingen van ${maand}` : ''}.\n    </span>\n  );\n}\n\n/**\n * Bij Diepte de keuze van het peil, bij een ontwerpdiepte de kleurschaal.\n *\n * Twee keuzes op dezelfde plek, omdat ze elkaar uitsluiten: een peil kent geen\n * klassen (de bodem ligt overal veertien meter onder NAP, dus alles valt in\n * dezelfde bak), en een ontwerpdiepte heeft geen tweede peil.\n */\nfunction Keuze({ onPeil }: { onPeil: (peil: Peil) => void }) {\n  const { state, dispatch } = useKaart();\n  // De verschilkaart heeft één vaste schaal en geen peil om te kiezen.\n  if (state.referenceLevel === 'verschil') return null;\n  const isPeil = !heeftColormapKeuze(state.referenceLevel);\n\n  const opties = isPeil\n    ? (\n        [\n          ['nap', 'NAP'],\n          ['lat', 'LAT'],\n        ] as const\n      ).map(([peil, label]) => ({\n        label,\n        gekozen: state.referenceLevel === peil,\n        kies: () => onPeil(peil),\n      }))\n    : COLORMAPS.map(([colormap, label]) => ({\n        label,\n        gekozen: state.colormap === colormap,\n        kies: () => dispatch({ type: 'SET_COLORMAP', colormap }),\n      }));\n\n  return (\n    <div\n      role=\"group\"\n      aria-label={isPeil ? 'Peil' : 'Kleurschaal'}\n      title={\n        isPeil\n          ? undefined\n          : 'Klassen volgen de indeling van de aanlevering; verloop toont dezelfde kleuren zonder harde grenzen. Kans kijkt één peiling vooruit: hoe waarschijnlijk ligt de bodem dan boven het vlak.'\n      }\n      className=\"flex shrink-0 gap-0.5 rounded-lg bg-muted p-[3px]\"\n    >\n      {opties.map((optie) => (\n        <button\n          key={optie.label}\n          type=\"button\"\n          aria-pressed={optie.gekozen}\n          onClick={optie.kies}\n          className={cn(\n            'h-7 rounded-md px-2 text-xs font-medium transition-colors',\n            optie.gekozen\n              ? 'bg-background text-primary shadow-xs'\n              : 'text-muted-foreground hover:text-foreground'\n          )}\n        >\n          {optie.label}\n        </button>\n      ))}\n    </div>\n  );\n}\n\n/**\n * Verklaart de hoogtelijnen (#114).\n *\n * De regel over generaliseren staat er niet voor de volledigheid. Op deze kaart\n * kunnen een contourlijn en een dieptecijfer naast elkaar staan die iets anders\n * lijken te zeggen over dezelfde plek, en dan moet duidelijk zijn welk van de\n * twee het gezaghebbende getal is.\n */\nfunction ContourRegel() {\n  const { state } = useKaart();\n  if (!state.layers.dieptecontouren || !heeftContouren(state.referenceLevel)) return null;\n\n  return (\n    <div className=\"flex flex-col gap-0.5\">\n      <div className=\"flex items-center gap-2\">\n        <span className=\"h-0 w-4 shrink-0 border-t-2\" style={{ borderColor: CONTOUR_KLEUR }} />\n        <span>\n          Hoogtelijn elke {CONTOUR_INTERVAL_M.toLocaleString('nl-NL')} m, hele meters dikker\n        </span>\n      </div>\n      <span>Lijnen zijn afgevlakt op {CONTOUR_GENERALISATIE_M} m; de dieptecijfers niet.</span>\n    </div>\n  );\n}\n\n/**\n * Verklaart de magenta streepjeslijn om een aantoonpeiling. Zonder deze regel\n * moet je weten wat die lijn betekent, en juist dat was de klacht: het is niet\n * duidelijk of aantoonpeilingen worden meegenomen. Zie #113.\n */\nfunction PeilsoortRegel() {\n  const { state } = useKaart();\n  if (!toontPeilsoort(state, 'atp')) return null;\n  return (\n    <div className=\"flex flex-col gap-0.5\">\n      <div className=\"flex items-center gap-2\">\n        <span\n          className=\"h-0 w-4 shrink-0 border-t-2 border-dashed\"\n          style={{ borderColor: ATP_OUTLINE_COLOR }}\n        />\n        <span>{PEILSOORT_LABELS.atp}</span>\n      </div>\n      <span>Dekt alleen het gebaggerde deel van een vak</span>\n    </div>\n  );\n}\n\n/** Welke maand de omgeving buiten de vakken laat zien (en of het een verwachting is). */\nfunction OmgevingRegel() {\n  const { state } = useKaart();\n  if (!state.layers.omgeving) return null;\n  const kaart = omgevingVoorDatum(\n    state.omgevingKaarten,\n    state.selectedDate,\n    isVoorspellingsDatum(state.selectedDate),\n    variantVoor(state.referenceLevel)\n  );\n  if (!kaart) return null;\n  return <span>{legendaTekst(kaart)}</span>;\n}\n\ninterface WerkbladKaartbeeldProps {\n  /** Het peil waar Diepte op uitkomt: het laatst gekozen van de twee. */\n  peil: Peil;\n  onPeil: (peil: Peil) => void;\n  onInklappen: () => void;\n}\n\n/**\n * Bovenaan het werkblad en altijd zichtbaar: wat de kleuren op de kaart\n * betekenen. De keuze en de legenda staan bij elkaar omdat de legenda meteen\n * laat zien wat de keuze doet.\n */\nexport default function WerkbladKaartbeeld({ peil, onPeil, onInklappen }: WerkbladKaartbeeldProps) {\n  const { state, dispatch } = useKaart();\n  const beeld = beeldVan(state.referenceLevel);\n\n  const kiesPeil = (nieuw: Peil) => {\n    onPeil(nieuw);\n    dispatch({ type: 'SET_REFERENCE_LEVEL', level: nieuw });\n  };\n  const kiesBeeld = (b: Beeld) =>\n    b === 'diepte' ? kiesPeil(peil) : dispatch({ type: 'SET_REFERENCE_LEVEL', level: b });\n  const gekozenBeeld = BEELDEN.findIndex((b) => b.beeld === beeld);\n\n  const aanvullend = [\n    state.referenceLevel === 'lat' && (\n      <span key=\"lat\">\n        LAT ligt per vak 1,61 tot 1,94 m onder NAP; de kaart telt het vlak van het vak waar je\n        kijkt, en buiten de vakken een vlak door die waarden.\n      </span>\n    ),\n    <ContourRegel key=\"contour\" />,\n    <PeilsoortRegel key=\"peilsoort\" />,\n    <OmgevingRegel key=\"omgeving\" />,\n  ];\n\n  return (\n    <section\n      aria-label=\"Kaartbeeld\"\n      className=\"flex shrink-0 flex-col gap-2.5 border-b bg-muted/40 px-4 pt-4 pb-3.5\"\n    >\n      <div className=\"flex items-center justify-between\">\n        <h2 className=\"text-[15px] font-semibold\">Kaartbeeld</h2>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-9 text-muted-foreground\"\n          aria-label=\"Werkblad inklappen\"\n          title=\"Inklappen\"\n          onClick={onInklappen}\n        >\n          <ChevronsRight className=\"size-[18px]\" />\n        </Button>\n      </div>\n\n      <div\n        role=\"radiogroup\"\n        aria-label=\"Wat wil je zien\"\n        className=\"grid grid-cols-4 gap-0.5 rounded-[10px] bg-muted p-[3px]\"\n        onKeyDown={(e) => {\n          const i = volgendeIndex(e.key, gekozenBeeld, BEELDEN.length);\n          if (i == null) return;\n          e.preventDefault();\n          kiesBeeld(BEELDEN[i].beeld);\n          e.currentTarget.querySelectorAll<HTMLElement>('[role=\"radio\"]')[i]?.focus();\n        }}\n      >\n        {BEELDEN.map((b) => {\n          const gekozen = b.beeld === beeld;\n          return (\n            <button\n              key={b.beeld}\n              type=\"button\"\n              role=\"radio\"\n              aria-checked={gekozen}\n              tabIndex={gekozen ? 0 : -1}\n              onClick={() => kiesBeeld(b.beeld)}\n              className={cn(\n                'flex min-h-[46px] flex-col items-center justify-center rounded-lg px-1 py-1 transition-colors',\n                gekozen ? 'bg-background shadow-xs' : 'hover:text-foreground'\n              )}\n            >\n              <span className={cn('text-[13px] font-semibold', gekozen && 'text-primary')}>\n                {b.kort}\n              </span>\n              {/* \"maximale diepte\" past niet op één regel in een kwart werkblad. */}\n              <span className=\"text-center text-[11px] leading-tight text-muted-foreground\">\n                {b.beeld === 'diepte' ? REFERENTIE_NAAM[peil] : b.sub}\n              </span>\n            </button>\n          );\n        })}\n      </div>\n\n      <Legenda />\n\n      <div className=\"flex items-center gap-2.5\">\n        <div className=\"min-w-0 flex-1\">\n          <Uitleg />\n        </div>\n        <Keuze onPeil={kiesPeil} />\n      </div>\n\n      {state.colormap === 'kans' && heeftColormapKeuze(state.referenceLevel) && (\n        <p className=\"text-xs leading-snug text-muted-foreground\">\n          Verwachting: de getoonde peiling. Onzekerheid: hoeveel de bodem in dit vak in een maand\n          verandert. Een vak zonder genoeg peilingen blijft leeg.\n        </p>\n      )}\n\n      <div className=\"flex flex-col gap-2 text-xs leading-snug text-muted-foreground empty:hidden [&>*+*]:border-t [&>*+*]:border-border [&>*+*]:pt-2\">\n        {aanvullend}\n      </div>\n    </section>\n  );\n}\n","import { ChevronRight } from 'lucide-react';\n\nimport { useKaart, toontPeilsoort, type Basemap, type KaartState } from './KaartContext';\nimport { REFERENTIE_NAAM } from './WerkbladKaartbeeld';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '@/js/components/ui/tooltip';\nimport { getLegendStops } from '@/js/lib/depth-colors';\nimport {\n  CONTOUR_GENERALISATIE_M,\n  CONTOUR_INTERVAL_M,\n  CONTOUR_KLEUR,\n  heeftContouren,\n} from '@/js/lib/dieptecontouren';\nimport { LAAG_KLEUR } from '@/js/lib/laagkleuren';\nimport { ATP_OUTLINE_COLOR, PEILSOORT_LABELS } from '@/js/lib/peilsoort';\nimport {\n  FASE_DEKKING,\n  FASE_KLEUR,\n  FASE_LABELS,\n  FASE_VOLGORDE,\n  STRIJDIG_LABEL,\n} from '@/js/lib/scheepsactiviteit';\nimport { SHALLOWEST_MARKER_COLORS } from '@/js/lib/shallowest-points';\nimport { cn } from '@/js/lib/utils';\n\nconst ONDERGRONDEN: { value: Basemap; label: string; tegel: string }[] = [\n  { value: 'light', label: 'Licht', tegel: 'linear-gradient(160deg, #f2f2f2 0 45%, #c9cbcd 45%)' },\n  {\n    value: 'standaard',\n    label: 'Kleur',\n    tegel: 'linear-gradient(160deg, #e6edd8 0 45%, #a9cbe3 45%)',\n  },\n  {\n    value: 'satellite',\n    label: 'Satelliet',\n    tegel: 'linear-gradient(160deg, #c9b78e 0 45%, #2e4d57 45%)',\n  },\n];\n\nconst DIEPTEVERLOOP = `linear-gradient(to right, ${getLegendStops(undefined, 8)\n  .map((s) => s.color)\n  .join(', ')})`;\n\n/** Een vlakje zoals de laag op de kaart oogt: rand, vulling, gestippeld of niet. */\nfunction vlak(kleur: string, { gestippeld = false, vulling = 0 } = {}): CSSProperties {\n  return {\n    width: 22,\n    height: 12,\n    borderRadius: 2,\n    border: `2px ${gestippeld ? 'dashed' : 'solid'} ${kleur}`,\n    backgroundColor: vulling\n      ? `color-mix(in srgb, ${kleur} ${vulling}%, transparent)`\n      : 'transparent',\n  };\n}\n\nfunction lijn(achtergrond: string, dikte: number): CSSProperties {\n  return { width: 22, height: dikte, borderRadius: 1, background: achtergrond };\n}\n\nconst SYMBOOL: Record<string, CSSProperties> = {\n  regulier: { width: 22, height: 12, borderRadius: 3, background: DIEPTEVERLOOP },\n  atp: vlak(ATP_OUTLINE_COLOR, { gestippeld: true }),\n  baggervakken: vlak(LAAG_KLEUR.baggervakken),\n  ondiepstePunten: {\n    width: 11,\n    height: 11,\n    borderRadius: '50%',\n    background: SHALLOWEST_MARKER_COLORS.red,\n    border: '2px solid #ffffff',\n    boxShadow: '0 0 0 1px rgba(0,0,0,.12)',\n  },\n  randstroken: vlak(LAAG_KLEUR.randstroken, { gestippeld: true, vulling: 20 }),\n  passeerstroken: vlak(LAAG_KLEUR.passeerstroken, { gestippeld: true }),\n  stortlocaties: vlak(LAAG_KLEUR.stortlocaties, { vulling: 25 }),\n  // Dezelfde dieptekleuren als de peiling, want zo staat de omgeving op de\n  // kaart; de stippelrand houdt hem in de lijst uit elkaar met de peiling.\n  omgeving: {\n    width: 22,\n    height: 12,\n    borderRadius: 3,\n    background: DIEPTEVERLOOP,\n    border: '1px dashed rgba(0, 0, 0, 0.45)',\n  },\n  vaargeul: vlak(LAAG_KLEUR.vaargeul, { vulling: 12 }),\n  taluds: vlak(LAAG_KLEUR.taluds, { gestippeld: true, vulling: 18 }),\n  relief: {\n    width: 22,\n    height: 12,\n    borderRadius: 3,\n    background: 'linear-gradient(135deg, #f2f2f2, #9a9a9a)',\n  },\n  dieptecijfers: { width: 22, height: 12, borderRadius: 2, border: '1px solid #9aa0a8' },\n  dieptecontouren: lijn(CONTOUR_KLEUR, 2),\n  scheepsactiviteit: lijn(\n    `linear-gradient(to right, ${FASE_KLEUR.baggeren} 0 50%, ${FASE_KLEUR.lossen} 50%)`,\n    3\n  ),\n};\n\ntype LaagSleutel = keyof KaartState['layers'];\n\n/**\n * Lagen die zelden aan hoeven en daarom achter \"Meer lagen\" staan. De teller\n * zegt hoeveel ervan aan staan, zodat een ingeklapte lijst niet verzwijgt dat\n * er iets op de kaart ligt.\n */\nexport const MEER_LAGEN: LaagSleutel[] = [\n  'vaargeul',\n  'taluds',\n  'relief',\n  'dieptecijfers',\n  'dieptecontouren',\n  'scheepsactiviteit',\n];\n\n/** Of een laag echt op de kaart staat: contouren tellen alleen tegen een peil. */\nfunction laagAan(state: KaartState, laag: LaagSleutel): boolean {\n  if (laag === 'dieptecontouren') {\n    return state.layers.dieptecontouren && heeftContouren(state.referenceLevel);\n  }\n  return state.layers[laag];\n}\n\nfunction isPeil(state: KaartState) {\n  return state.referenceLevel === 'nap' || state.referenceLevel === 'lat';\n}\n\ninterface WerkbladLagenProps {\n  meer: boolean;\n  onMeer: (meer: boolean) => void;\n}\n\n/** Tabblad Lagen: één lijst, elke regel het symbool zoals op de kaart, de naam en een schakelaar. */\nexport default function WerkbladLagen({ meer, onMeer }: WerkbladLagenProps) {\n  const { state, dispatch } = useKaart();\n  const peil = REFERENTIE_NAAM[state.referenceLevel];\n  const regulier = toontPeilsoort(state, 'regulier');\n  const wissel = (layer: LaagSleutel) => () => dispatch({ type: 'TOGGLE_LAYER', layer });\n\n  const hoofdlagen: LaagSleutel[] = [\n    'baggervakken',\n    'ondiepstePunten',\n    'randstroken',\n    'passeerstroken',\n    'stortlocaties',\n    'omgeving',\n  ];\n  const aanMeer = MEER_LAGEN.filter((laag) => laagAan(state, laag)).length;\n  const aantalAan =\n    state.peilsoorten.length + hoofdlagen.filter((laag) => laagAan(state, laag)).length + aanMeer;\n\n  const laag = (sleutel: LaagSleutel, naam: string, uitleg?: string) => (\n    <LaagRegel\n      naam={naam}\n      symbool={SYMBOOL[sleutel]}\n      aan={laagAan(state, sleutel)}\n      onWissel={wissel(sleutel)}\n      uitleg={uitleg}\n    />\n  );\n\n  return (\n    <TooltipProvider delayDuration={300}>\n      <div className=\"flex flex-col\">\n        <div className=\"flex flex-col px-2 pt-3 pb-1\">\n          <div className=\"flex justify-between px-2 pb-1.5 text-xs text-muted-foreground\">\n            <span className=\"font-semibold\">Op de kaart</span>\n            <span className=\"getal\">{aantalAan} aan</span>\n          </div>\n\n          <LaagRegel\n            naam={PEILSOORT_LABELS.regulier}\n            symbool={SYMBOOL.regulier}\n            aan={regulier}\n            onWissel={() => dispatch({ type: 'TOGGLE_PEILSOORT', peilsoort: 'regulier' })}\n            uitleg=\"Periodieke peiling over het hele baggervak\"\n          />\n          <LaagRegel\n            naam={PEILSOORT_LABELS.atp}\n            symbool={SYMBOOL.atp}\n            aan={toontPeilsoort(state, 'atp')}\n            onWissel={() => dispatch({ type: 'TOGGLE_PEILSOORT', peilsoort: 'atp' })}\n            uitleg=\"Bewijspeiling na baggeren, dekt alleen het gebaggerde deel van het vak\"\n          />\n          {state.peilsoorten.length === 0 && (\n            <p className=\"px-2 pb-1 text-xs text-muted-foreground\">\n              Geen peilsoort aan, dus er is geen bodem zichtbaar.\n            </p>\n          )}\n\n          {laag('baggervakken', 'Baggervakken')}\n          {state.layers.baggervakken && (\n            <button\n              type=\"button\"\n              role=\"switch\"\n              aria-checked={state.clipToBaggervakken}\n              disabled={!regulier}\n              onClick={() => dispatch({ type: 'TOGGLE_CLIP' })}\n              className=\"ml-[38px] flex h-8 items-center gap-3 rounded-lg px-2 text-left text-xs text-muted-foreground hover:bg-accent disabled:cursor-not-allowed disabled:hover:bg-transparent\"\n            >\n              <span className=\"min-w-0 flex-1\">Clip naar vakken</span>\n              {/* Zelfde reden als bij de contouren: een schakelaar die zonder\n                  uitleg niet reageert, laat de gebruiker zoeken. */}\n              {!regulier && <span className=\"shrink-0\">alleen met periodieke peiling</span>}\n              <Schakelaar aan={state.clipToBaggervakken} uitgeschakeld={!regulier} />\n            </button>\n          )}\n          {laag(\n            'ondiepstePunten',\n            'Ondiepste punt per vak',\n            'De ondiepste plek per vak, uit de laatste periodieke peiling'\n          )}\n          {laag('randstroken', '5 m-strook')}\n          {laag('passeerstroken', 'Passeerstroken')}\n          {laag('stortlocaties', 'Stortlocaties')}\n          {laag(\n            'omgeving',\n            'Omgeving actueel',\n            isPeil(state)\n              ? 'De bodem rond de vakken, elke maand bijgewerkt met vaklodingen, peilingen en heldere satellietfoto’s. Wanneer een plek voor het laatst gezien is, zie je bij de speld na een klik op de kaart.'\n              : 'Diepte en zekerheid van de omgeving zie je in NAP.'\n          )}\n\n          <button\n            type=\"button\"\n            aria-expanded={meer}\n            onClick={() => onMeer(!meer)}\n            className=\"flex h-9 items-center gap-3 rounded-lg px-2 text-left text-[13px] font-medium text-primary hover:bg-accent\"\n          >\n            <span className=\"flex w-[26px] justify-center\">\n              <ChevronRight className={cn('size-4 transition-transform', meer && 'rotate-90')} />\n            </span>\n            {meer\n              ? 'Minder lagen'\n              : `Meer lagen (${MEER_LAGEN.length}${aanMeer ? `, ${aanMeer} aan` : ''})`}\n          </button>\n\n          {/* Een fout of verklaring van een laag die aan staat hoort niet achter\n              een dichtgeklapte lijst te vallen: dan zegt alleen de teller nog\n              dat er iets op de kaart hoort te liggen. */}\n          {!meer && (\n            <>\n              <LaagMelding laag=\"dieptecijfers\" naam=\"Dieptecijfers\" />\n              <LaagMelding laag=\"dieptecontouren\" naam=\"Dieptecontouren\" />\n              {state.layers.scheepsactiviteit && <ScheepsactiviteitMeldingen metNaam />}\n            </>\n          )}\n\n          {meer && (\n            <>\n              {laag(\n                'vaargeul',\n                'Vaargeul',\n                'De vaargeul zonder de 5 m-strook; elk hoekpunt draagt zijn eigen NGD.'\n              )}\n              {laag('taluds', 'Taluds', 'Hellingsvlakken tussen passeerstrook en vaargeul')}\n              {laag(\n                'relief',\n                'Reliëf',\n                'Schaduw over de dieptekleuren, met de bodem acht keer overdreven. Maakt duinen en baggersporen zichtbaar; speel de tijdlijn af om ze te zien bewegen.'\n              )}\n              {laag(\n                'dieptecijfers',\n                'Dieptecijfers',\n                isPeil(state)\n                  ? `Diepte in m ${peil}, per cel het ondiepste gemeten punt`\n                  : state.referenceLevel === 'verschil'\n                    ? // De verschilkaart kleurt de verandering; de cijfers blijven de diepte.\n                      'Diepte in m NAP, per cel het ondiepste gemeten punt'\n                    : state.referenceLevel === 'ohd'\n                      ? 'Verschil met de maximale diepte, per cel het ondiepste gemeten punt'\n                      : 'Verschil met het NGD-vlak, per cel het ondiepste gemeten punt'\n              )}\n              <LaagMelding laag=\"dieptecijfers\" />\n              <LaagRegel\n                naam=\"Dieptecontouren\"\n                symbool={SYMBOOL.dieptecontouren}\n                aan={laagAan(state, 'dieptecontouren')}\n                onWissel={wissel('dieptecontouren')}\n                uitgeschakeld={!heeftContouren(state.referenceLevel)}\n                reden=\"alleen bij Diepte\"\n                uitleg={\n                  heeftContouren(state.referenceLevel)\n                    ? `Hoogtelijnen elke ${CONTOUR_INTERVAL_M.toLocaleString('nl-NL')} m in ${peil}, afgeleid op ${CONTOUR_GENERALISATIE_M} m`\n                    : 'Hoogtelijnen horen bij een peil. Kies Diepte (NAP of LAT); op een verschilkaart tekent de kaart de klassen al in kleur.'\n                }\n              />\n              <LaagMelding laag=\"dieptecontouren\" />\n              {laag(\n                'scheepsactiviteit',\n                'Scheepsactiviteit',\n                'Vaartracks van de hoppers tussen de vorige en de getoonde peiling, gekleurd naar wat het schip deed.'\n              )}\n              {state.layers.scheepsactiviteit && <ScheepsactiviteitUitleg />}\n            </>\n          )}\n        </div>\n\n        <div className=\"mt-1.5 flex flex-col gap-2 border-t px-4 pt-3 pb-4\">\n          <span className=\"text-xs font-semibold text-muted-foreground\">Ondergrond</span>\n          <div className=\"grid grid-cols-3 gap-2\">\n            {ONDERGRONDEN.map((o) => {\n              const gekozen = state.basemap === o.value;\n              return (\n                <button\n                  key={o.value}\n                  type=\"button\"\n                  aria-pressed={gekozen}\n                  onClick={() => dispatch({ type: 'SET_BASEMAP', basemap: o.value })}\n                  className={cn(\n                    'flex flex-col gap-1 text-left text-xs font-medium',\n                    gekozen ? 'text-primary' : 'text-muted-foreground hover:text-foreground'\n                  )}\n                >\n                  <span\n                    className={cn(\n                      'h-10 rounded-lg',\n                      gekozen\n                        ? 'ring-2 ring-primary ring-offset-2 ring-offset-background'\n                        : 'ring-1 ring-black/10 ring-inset'\n                    )}\n                    style={{ background: o.tegel }}\n                  />\n                  {o.label}\n                </button>\n              );\n            })}\n          </div>\n        </div>\n      </div>\n    </TooltipProvider>\n  );\n}\n\n/** De fase-legenda en de meldingen van de scheepsactiviteit, direct onder die regel. */\nfunction ScheepsactiviteitUitleg() {\n  return (\n    <div className=\"ml-[46px] flex flex-col gap-1 pb-2\">\n      <ul\n        aria-label=\"Legenda scheepsactiviteit\"\n        className=\"flex flex-col gap-0.5 text-xs text-muted-foreground\"\n      >\n        {FASE_VOLGORDE.map((fase) => (\n          <li key={fase} className=\"flex items-center gap-1.5\">\n            <span\n              className=\"h-0 w-3.5 shrink-0 border-t-2\"\n              // Dezelfde dekking als op de kaart: een vol streepje naast een\n              // halfdoorzichtige lijn laat de legenda een kleur beloven die er\n              // niet staat.\n              style={{ borderColor: FASE_KLEUR[fase], opacity: FASE_DEKKING[fase] }}\n            />\n            <span>{FASE_LABELS[fase]}</span>\n          </li>\n        ))}\n        {/* Geen vijfde fase maar een waarschuwing: het schip is er tijdens die\n            fase niet geweest. Zichtbaar houden en niet wegpoetsen, want\n            dezelfde reisregels leveren de kuubs per vak. */}\n        <li className=\"flex items-center gap-1.5\">\n          <span\n            className=\"h-0 w-3.5 shrink-0 border-t-2 border-dashed\"\n            style={{ borderColor: FASE_KLEUR.baggeren, opacity: FASE_DEKKING.baggeren }}\n          />\n          <span>{STRIJDIG_LABEL}</span>\n        </li>\n      </ul>\n      <ScheepsactiviteitMeldingen />\n    </div>\n  );\n}\n\n/**\n * De verklaring en de fout van de scheepsactiviteit. Alleen aangeroepen als de\n * laag aan staat: wie de laag nooit aanzette hoort geen rode tekst te krijgen\n * over een laag die hij niet kent.\n *\n * `metNaam` voor onder een dichtgeklapte \"Meer lagen\": daar staat de regel van\n * de laag er niet boven, dus zegt de verklaring zelf over welke laag het gaat.\n * De fout noemt de laag al.\n */\nfunction ScheepsactiviteitMeldingen({ metNaam = false }: { metNaam?: boolean }) {\n  const { state } = useKaart();\n  const melding = state.laagmeldingen.scheepsactiviteit;\n  const fout = state.laagfouten.scheepsactiviteit;\n  if (!melding && !fout) return null;\n\n  return (\n    <div className={cn('flex flex-col gap-1', metNaam && 'ml-[46px] pb-2')}>\n      {/* Geen fout maar een verklaring: de laag is leeg omdat de AIS-aanlevering\n          hier niet toe reikt of omdat er niet gevaren is. Zonder deze regel is\n          een lege laag niet te onderscheiden van een kapotte. */}\n      {melding && (\n        <p className=\"text-xs leading-snug text-muted-foreground\">\n          {metNaam && <span className=\"font-medium\">Scheepsactiviteit: </span>}\n          {melding}\n        </p>\n      )}\n      {fout && <p className=\"text-xs leading-snug text-destructive\">{fout}</p>}\n    </div>\n  );\n}\n\n/**\n * Wat een laag nog aan het doen is of waar hij minder zegt dan hij lijkt\n * (MapView zet het in de context). Stond eerder linksonder over de kaart, los\n * van de laag waar het over ging.\n *\n * `naam` voor onder een dichtgeklapte \"Meer lagen\", net als bij de\n * scheepsactiviteit: daar staat de regel van de laag er niet boven. Een\n * melding die de laag al noemt (\"Dieptecijfers berekenen…\") krijgt hem niet\n * nog eens ervoor.\n */\nfunction LaagMelding({ laag, naam }: { laag: 'dieptecijfers' | 'dieptecontouren'; naam?: string }) {\n  const { state } = useKaart();\n  const melding = state.laagmeldingen[laag];\n  if (!melding) return null;\n  return (\n    <p role=\"status\" className=\"ml-[46px] pr-2 pb-2 text-xs leading-snug text-muted-foreground\">\n      {naam && !melding.startsWith(naam) && <span className=\"font-medium\">{naam}: </span>}\n      {melding}\n    </p>\n  );\n}\n\nfunction LaagRegel({\n  naam,\n  symbool,\n  aan,\n  onWissel,\n  uitleg,\n  uitgeschakeld = false,\n  reden,\n}: {\n  naam: string;\n  symbool: CSSProperties;\n  aan: boolean;\n  onWissel: () => void;\n  uitleg?: string;\n  /**\n   * De laag kan hier niet aan. De regel blijft staan met de reden erbij, want\n   * een regel die verdwijnt laat de gebruiker zoeken naar iets dat er gisteren\n   * nog was.\n   */\n  uitgeschakeld?: boolean;\n  reden?: string;\n}) {\n  const regel = (\n    <button\n      type=\"button\"\n      role=\"switch\"\n      aria-checked={aan}\n      disabled={uitgeschakeld}\n      onClick={onWissel}\n      className={cn(\n        'flex h-9 w-full items-center gap-3 rounded-lg px-2 text-left text-[13px] hover:bg-accent disabled:cursor-not-allowed disabled:hover:bg-transparent',\n        aan ? 'text-foreground' : 'text-muted-foreground'\n      )}\n    >\n      <span\n        aria-hidden\n        className={cn(\n          'flex h-4 w-[26px] shrink-0 items-center justify-center',\n          !aan && 'opacity-45'\n        )}\n      >\n        <span className=\"box-border block\" style={symbool} />\n      </span>\n      <span className=\"min-w-0 flex-1 truncate\">{naam}</span>\n      {uitgeschakeld && reden && <span className=\"shrink-0 text-xs\">{reden}</span>}\n      <Schakelaar aan={aan} uitgeschakeld={uitgeschakeld} />\n    </button>\n  );\n\n  if (!uitleg) return regel;\n  return (\n    <Tooltip>\n      {/* Een uitgeschakelde knop krijgt geen pointer-events, dus dan zou juist\n          de uitleg wegvallen die vertelt waaróm hij uit staat. De wikkel vangt\n          die events op. */}\n      <TooltipTrigger asChild>\n        {uitgeschakeld ? <span className=\"block w-full\">{regel}</span> : regel}\n      </TooltipTrigger>\n      <TooltipContent side=\"left\" className=\"max-w-64 text-xs\">\n        {uitleg}\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n\n/** Alleen het beeld van een schakelaar; de hele regel is de knop. */\nfunction Schakelaar({ aan, uitgeschakeld }: { aan: boolean; uitgeschakeld: boolean }): ReactNode {\n  return (\n    <span\n      aria-hidden\n      className={cn(\n        'relative h-[18px] w-[30px] shrink-0 rounded-full transition-colors',\n        aan ? 'bg-primary' : 'bg-input',\n        uitgeschakeld && 'opacity-50'\n      )}\n    >\n      <span\n        className={cn(\n          'absolute top-0.5 size-3.5 rounded-full bg-background shadow-sm transition-[left]',\n          aan ? 'left-3.5' : 'left-0.5'\n        )}\n      />\n    </span>\n  );\n}\n","import { X } from 'lucide-react';\nimport { Link } from 'react-router';\n\nimport { useKaart } from './KaartContext';\n\nimport type { SurveyDataset } from '@/js/api';\n\nimport { useAuth } from '@/js/auth';\nimport { Button } from '@/js/components/ui/button';\nimport { getal } from '@/js/lib/getal-notatie';\nimport { plekNaam, type KaartObject } from '@/js/lib/kaart-object';\nimport { cn } from '@/js/lib/utils';\nimport { cmAfgerond } from '@/js/pages/analyse/VakOverviewTable';\n\ninterface WerkbladVakProps {\n  /** De baggervakken van de kaart; daaruit komen de vlakken van het gekozen vak. */\n  baggervakken: GeoJSON.FeatureCollection | null;\n  /** De laatste periodieke peiling per vak, één keer opgehaald door het werkblad. */\n  laatstePeilingen: {\n    latestByVak: Map<string, SurveyDataset>;\n    loading: boolean;\n    fout: boolean;\n  };\n  /**\n   * De peilingen die nu op de kaart staan (de gekozen datum). Daaruit leest de\n   * verschilkaart of dit vak een vorige peiling heeft.\n   */\n  opDeKaart?: SurveyDataset[];\n}\n\n/**\n * Tabblad Vak: het vak waar op de kaart in geklikt is.\n *\n * De kuubs en het ondiepste punt komen uit dezelfde bron als het overzicht op\n * de analysepagina (de laatste reguliere peiling per vak), zodat de twee\n * schermen nooit een ander getal voor hetzelfde vak laten zien. Ze staan er\n * alleen voor wie ook die pagina mag zien.\n */\nexport default function WerkbladVak({\n  baggervakken,\n  laatstePeilingen,\n  opDeKaart = [],\n}: WerkbladVakProps) {\n  const { state, dispatch } = useKaart();\n  const { user } = useAuth();\n  const { latestByVak, loading, fout } = laatstePeilingen;\n  const code = state.selectedVak;\n  const selectie = state.selectie;\n\n  if (selectie?.soort === 'stortlocatie') {\n    return (\n      <Stortlocatie\n        object={selectie}\n        onSluit={() => dispatch({ type: 'SELECT_OBJECT', object: null })}\n      />\n    );\n  }\n\n  if (!code) {\n    return (\n      <p className=\"px-5 py-6 text-[13px] text-muted-foreground\">Klik op een vak op de kaart.</p>\n    );\n  }\n\n  const eigenschappen =\n    baggervakken?.features.find((f) => f.properties?.code === code)?.properties ?? {};\n  const niveau = (sleutel: string) => (eigenschappen[sleutel] as number | null | undefined) ?? null;\n  const peiling = latestByVak.get(code) ?? null;\n  const cm = peiling?.cm_above_ngd ?? null;\n  // Het oordeel volgt het getal zoals het er staat, net als in de vaktabel en\n  // de vakkenlijn; anders noemt dit scherm een vak boven NGD dat de\n  // analysepagina \"op diepte\" noemt.\n  const cmGetoond = cm == null ? null : cmAfgerond(cm);\n  const analyse = user?.has_analyse_access === true;\n\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"flex flex-col gap-2.5 border-b px-5 pt-[18px] pb-3.5\">\n        <div className=\"flex items-start justify-between\">\n          <div className=\"flex flex-col gap-0.5\">\n            <span className=\"text-xs text-muted-foreground\">Baggervak</span>\n            <h3 className=\"text-[28px] leading-tight font-semibold tracking-tight\">{code}</h3>\n          </div>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-8 text-muted-foreground\"\n            aria-label=\"Vak sluiten\"\n            title=\"Vak sluiten\"\n            onClick={() => dispatch({ type: 'SELECT_OBJECT', object: null })}\n          >\n            <X className=\"size-4\" />\n          </Button>\n        </div>\n        {/* Geklikt op een strook of talud: het vak hieronder is het vak waar die\n            bij hoort, en de vorm zelf heeft eigen ontwerpdieptes. */}\n        {selectie && selectie.soort !== 'vak' && (\n          <div className=\"flex flex-col gap-1 rounded-lg bg-muted px-3 py-2 text-[13px]\">\n            <span>\n              Geklikt in {selectie.soort === 'talud' ? 'het' : 'de'}{' '}\n              <span className=\"font-semibold\">{plekNaam(selectie)}</span>\n            </span>\n            {eigenVlakken(selectie) && (\n              <span className=\"getal text-xs text-muted-foreground\">{eigenVlakken(selectie)}</span>\n            )}\n          </div>\n        )}\n        {peiling ? (\n          <p className=\"flex flex-wrap items-center gap-x-2 text-[13px]\">\n            {cmGetoond != null && (\n              <>\n                <span\n                  aria-hidden\n                  className={cn(\n                    'size-2 rounded-full',\n                    cmGetoond > 0 ? 'bg-destructive' : 'bg-muted-foreground/40'\n                  )}\n                />\n                {/* Plex Mono is alleen in 400 en 500 geladen; semibold op het\n                    getal zou een nagemaakte vette letter geven. */}\n                {cmGetoond === 0 ? (\n                  <span className=\"font-semibold\">Op NGD</span>\n                ) : (\n                  <span className={cn(cmGetoond > 0 && 'text-destructive')}>\n                    <span className=\"getal font-medium\">{getal(Math.abs(cmGetoond), 1)}</span>{' '}\n                    <span className=\"font-semibold\">\n                      cm {cmGetoond > 0 ? 'boven' : 'onder'} NGD\n                    </span>\n                  </span>\n                )}\n              </>\n            )}\n            {peiling.survey_date && (\n              <span className=\"text-muted-foreground\">\n                {cmGetoond != null && '· '}peiling {datumKort(peiling.survey_date)}\n              </span>\n            )}\n          </p>\n        ) : (\n          // Laden en een mislukte fetch zijn geen \"nooit gepeild\": dat zou een\n          // uitspraak over het vak zijn die de data niet doet.\n          <p className={cn('text-[13px]', fout ? 'text-destructive' : 'text-muted-foreground')}>\n            {loading\n              ? 'Peiling ophalen…'\n              : fout\n                ? 'De peilingen konden niet worden opgehaald.'\n                : 'Nog geen periodieke peiling.'}\n          </p>\n        )}\n        {state.referenceLevel === 'verschil' && (\n          <VerschilRegel peiling={opDeKaart.find((ds) => ds.baggervak_code === code) ?? null} />\n        )}\n        {/* Waar de peiling vandaan komt, zoals ingevuld bij het uploaden (bijv.\n            \"RWS-jaarpeiling 2019\"). Sinds de historische jaren erin staan is\n            dat niet meer vanzelf de aannemer. */}\n        {peiling?.bron && <p className=\"text-xs text-muted-foreground\">Bron: {peiling.bron}</p>}\n      </div>\n\n      {analyse && peiling && (\n        <>\n          <div className=\"grid grid-cols-2 gap-3 border-b px-5 py-4\">\n            {(\n              [\n                ['Boven NGD', peiling.volume_above_ngd_m3],\n                ['Boven max.', peiling.volume_above_ohd_m3],\n              ] as const\n            ).map(([label, m3]) => (\n              <div key={label} className=\"flex flex-col gap-1\">\n                <span className=\"text-xs text-muted-foreground\">{label}</span>\n                <span className=\"getal text-lg font-medium\">{m3 != null ? getal(m3) : '—'}</span>\n                <span className=\"text-xs text-muted-foreground\">m³</span>\n              </div>\n            ))}\n          </div>\n          <dl className=\"flex flex-col gap-2.5 border-b px-5 py-4 text-[13px]\">\n            <Regel\n              label=\"Ondiepste punt, met 5 m-strook\"\n              waarde={peiling.depth_max_incl_randstrook}\n            />\n            <Regel label=\"Ondiepste punt, zonder\" waarde={peiling.depth_max_in_vak} />\n          </dl>\n        </>\n      )}\n\n      <dl className=\"flex flex-col gap-1 px-5 py-4 text-[13px]\">\n        {VLAKKEN.map(([vlak, label]) => (\n          <Regel\n            key={vlak}\n            label={label}\n            waarde={niveau(`${vlak}_level`)}\n            gekozen={state.referenceLevel === vlak}\n          />\n        ))}\n        {/* LAT staat onder een streep: het is een peil en geen ontwerpdiepte,\n            dus het hoort niet in hetzelfde rijtje te lijken (#114). */}\n        <div className=\"mt-1 border-t pt-1\">\n          <Regel\n            label=\"LAT\"\n            waarde={niveau('lat_level')}\n            gekozen={state.referenceLevel === 'lat'}\n          />\n        </div>\n      </dl>\n\n      <div className=\"flex gap-2 px-5 pb-5\">\n        <Button\n          variant=\"outline\"\n          className=\"h-[38px] flex-1\"\n          onClick={() => dispatch({ type: 'START_CROSS_SECTION' })}\n        >\n          Dwarsprofiel\n        </Button>\n        {analyse && (\n          <Button asChild className=\"h-[38px] flex-1\">\n            <Link to={`/app/analyse?vak=${encodeURIComponent(code)}`}>Naar analyse</Link>\n          </Button>\n        )}\n      </div>\n    </div>\n  );\n}\n\nfunction Regel({\n  label,\n  waarde,\n  gekozen,\n}: {\n  label: string;\n  waarde: number | null | undefined;\n  gekozen?: boolean;\n}) {\n  // -mx-2 px-2 op elke regel: de markering van het gekozen vlak steekt uit, en\n  // de tekst lijnt overal uit op de rand van het tabblad.\n  return (\n    <div\n      className={cn(\n        '-mx-2 flex justify-between gap-3 rounded-md px-2',\n        gekozen !== undefined && 'py-1',\n        gekozen && 'bg-gekozen font-medium'\n      )}\n    >\n      <dt className=\"text-muted-foreground\">{label}</dt>\n      <dd className=\"getal\">{waarde != null ? `${getal(waarde, 2)} m NAP` : '—'}</dd>\n    </div>\n  );\n}\n\n/**\n * De ontwerpdieptes van het bestek 2026. IVD komt daar niet meer in voor; het\n * veld staat nog op het vak, maar het werkblad toont het niet.\n */\nconst VLAKKEN = [\n  ['ngd', 'Minimale diepte (NGD)'],\n  ['ohd', 'Maximale diepte'],\n] as const;\n\n/**\n * Bij de verschilkaart: waartegen dit vak is afgezet, of waarom het leeg is.\n * Leest de peiling die op de kaart staat, niet de laatste: de kaart volgt de\n * tijdlijn.\n */\nfunction VerschilRegel({ peiling }: { peiling: SurveyDataset | null }) {\n  if (!peiling) return null;\n  if (peiling.vorige_peiling_id != null && peiling.vorige_peiling_datum) {\n    // Er geldt geen termijn (bestek: \"t.o.v. de voorgaande Survey\"); het\n    // aantal dagen laat zien hoe ver de vorige peiling terugligt.\n    const dagen = dagenTussen(peiling.vorige_peiling_datum, peiling.survey_date);\n    return (\n      <p className=\"text-xs text-muted-foreground\">\n        Verschil met de peiling van {datumKort(peiling.vorige_peiling_datum)}\n        {dagen != null && ` (${dagen} dagen eerder)`}\n      </p>\n    );\n  }\n  return (\n    <p className=\"rounded-md bg-muted px-2.5 py-1.5 text-xs\">\n      {peiling.peilsoort === 'voorspelling'\n        ? 'Een voorspelling heeft geen vorige peiling: dit vak blijft leeg op de verschilkaart.'\n        : peiling.herkomst === 'rws_jaarpeiling'\n          ? 'Een jaarpeiling van RWS wordt niet vergeleken: dit vak blijft leeg op de verschilkaart.'\n          : 'Geen eerdere peiling van dit vak: het blijft leeg op de verschilkaart.'}\n    </p>\n  );\n}\n\n/** Hele dagen van `van` tot `tot` (ISO-datums), of null zonder `tot`. */\nfunction dagenTussen(van: string, tot: string | null | undefined): number | null {\n  if (!tot) return null;\n  return Math.round((Date.parse(`${tot}T00:00:00Z`) - Date.parse(`${van}T00:00:00Z`)) / 86_400_000);\n}\n\n/** \"NGD −12,00 · max. −12,50 m NAP\", of null zonder vlakken. */\nfunction eigenVlakken(object: KaartObject): string | null {\n  const delen = (\n    [\n      ['ngd', 'NGD'],\n      ['ohd', 'max.'],\n    ] as const\n  ).flatMap(([vlak, naam]) => {\n    const waarde = object.niveaus?.[vlak];\n    return waarde != null ? [`${naam} ${getal(waarde, 2)}`] : [];\n  });\n  return delen.length > 0 ? `${delen.join(' · ')} m NAP` : null;\n}\n\n/** Een stortlocatie hoort bij geen vak: alleen naam, oppervlak en omschrijving. */\nfunction Stortlocatie({ object, onSluit }: { object: KaartObject; onSluit: () => void }) {\n  return (\n    <div className=\"flex flex-col gap-2.5 px-5 pt-[18px] pb-5\">\n      <div className=\"flex items-start justify-between\">\n        <div className=\"flex flex-col gap-0.5\">\n          <span className=\"text-xs text-muted-foreground\">Stortlocatie</span>\n          <h3 className=\"text-[28px] leading-tight font-semibold tracking-tight\">{object.code}</h3>\n        </div>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-8 text-muted-foreground\"\n          aria-label=\"Stortlocatie sluiten\"\n          title=\"Stortlocatie sluiten\"\n          onClick={onSluit}\n        >\n          <X className=\"size-4\" />\n        </Button>\n      </div>\n      {object.oppervlakM2 != null && (\n        <p className=\"text-[13px]\">\n          Oppervlak{' '}\n          <span className=\"getal font-medium\">{getal(object.oppervlakM2 / 10_000, 1)}</span> ha\n        </p>\n      )}\n      {/* In de data is de omschrijving vaak gewoon de naam nog eens. */}\n      {object.omschrijving && object.omschrijving !== object.naam && (\n        <p className=\"text-[13px] text-muted-foreground\">{object.omschrijving}</p>\n      )}\n    </div>\n  );\n}\n\n/** \"14 sep 2026\": kort genoeg voor één regel naast de afstand tot de NGD. */\nfunction datumKort(datum: string): string {\n  return new Date(`${datum}T00:00:00`)\n    .toLocaleDateString('nl-NL', { day: 'numeric', month: 'short', year: 'numeric' })\n    .replace('.', '');\n}\n","import { ChevronsLeft, Hexagon, Layers, SquareStack } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { useKaart } from './KaartContext';\nimport WerkbladGebied, { useGebiedStatistiek } from './WerkbladGebied';\nimport WerkbladKaartbeeld, {\n  REFERENTIE_NAAM,\n  REFERENTIE_ZIN,\n  legendaBalk,\n  type Peil,\n} from './WerkbladKaartbeeld';\nimport WerkbladLagen from './WerkbladLagen';\nimport WerkbladVak from './WerkbladVak';\n\nimport type { SurveyDataset } from '@/js/api';\nimport type { MapRef } from 'react-map-gl/maplibre';\n\nimport { useLatestDatasets } from '@/js/hooks/use-latest-datasets';\nimport { heeftColormapKeuze } from '@/js/lib/depth-colors';\nimport { volgendeIndex } from '@/js/lib/pijltoetsen';\nimport { cn } from '@/js/lib/utils';\n\nexport type WerkbladTab = 'lagen' | 'vak' | 'gebied';\n\nexport interface WerkbladStand {\n  open: boolean;\n  tab: WerkbladTab;\n  /** Of \"Meer lagen\" in het tabblad Lagen uitgeklapt staat. */\n  meerLagen: boolean;\n}\n\nconst STAND_SLEUTEL = 'kaart.werkblad';\n\n/**\n * Of de kaart naast een open werkblad nog breed genoeg is. Op een telefoon of\n * een staande tablet laat een open werkblad van de kaart maar een strook over.\n */\nfunction isBreedScherm(): boolean {\n  try {\n    return window.matchMedia?.('(min-width: 1024px)').matches ?? true;\n  } catch {\n    // Geen matchMedia: dan geldt het brede scherm.\n    return true;\n  }\n}\n\n/** Open alleen op een breed scherm. */\nfunction standaard(): WerkbladStand {\n  return { open: isBreedScherm(), tab: 'lagen', meerLagen: false };\n}\n\nfunction leesStand(): WerkbladStand {\n  const basis = standaard();\n  try {\n    const opgeslagen = JSON.parse(localStorage.getItem(STAND_SLEUTEL) ?? 'null');\n    if (!opgeslagen || typeof opgeslagen !== 'object') return basis;\n    return {\n      open: typeof opgeslagen.open === 'boolean' ? opgeslagen.open : basis.open,\n      tab: ['lagen', 'vak', 'gebied'].includes(opgeslagen.tab) ? opgeslagen.tab : basis.tab,\n      meerLagen: opgeslagen.meerLagen === true,\n    };\n  } catch {\n    return basis;\n  }\n}\n\n/**\n * Open of dicht, en welk tabblad. Hoort bij de kaartpagina en niet bij het\n * werkblad zelf, omdat een klik op de kaart (in een vak) en het gereedschap\n * (gebied meten) het werkblad op een tabblad openen.\n *\n * Bewaard in localStorage, maar alleen als gemak: zonder opslag begint het\n * werkblad op Lagen, open of dicht naar de schermbreedte. Alleen wat de\n * gebruiker zelf kiest wordt bewaard; anders blijft de dichte stand van een\n * smal venster ook op het grote scherm hangen.\n */\nexport function useWerkbladStand() {\n  const [stand, setStand] = useState<WerkbladStand>(leesStand);\n  const [gekozen, setGekozen] = useState(false);\n\n  useEffect(() => {\n    if (!gekozen) return;\n    try {\n      localStorage.setItem(STAND_SLEUTEL, JSON.stringify(stand));\n    } catch {\n      // Geen opslag (privévenster, geblokkeerd): dan onthoudt het werkblad niets.\n    }\n  }, [stand, gekozen]);\n\n  const zet = useCallback((wijziging: Partial<WerkbladStand>) => {\n    setGekozen(true);\n    setStand((oud) => ({ ...oud, ...wijziging }));\n  }, []);\n  // Onder lg ligt het werkblad over de kaart. Vanzelf openklappen bij een\n  // vakklik of bij gebied meten zou dan de speld en de geul afdekken, juist\n  // waar je net klikt of gaat tekenen; daar kiest het alleen het tabblad.\n  const openOp = useCallback(\n    (tab: WerkbladTab) => zet(isBreedScherm() ? { open: true, tab } : { tab }),\n    [zet]\n  );\n\n  return { stand, zet, openOp };\n}\n\nconst TABS: { tab: WerkbladTab; label: string; Icoon: typeof Layers }[] = [\n  { tab: 'lagen', label: 'Lagen', Icoon: Layers },\n  { tab: 'vak', label: 'Vak', Icoon: SquareStack },\n  { tab: 'gebied', label: 'Gebied', Icoon: Hexagon },\n];\n\ninterface WerkbladProps {\n  stand: WerkbladStand;\n  zet: (wijziging: Partial<WerkbladStand>) => void;\n  mapRef: React.RefObject<MapRef | null>;\n  /** De periodieke peilingen die nu op de kaart staan; het meetgebied rekent daarop. */\n  datasets: SurveyDataset[];\n  baggervakken: GeoJSON.FeatureCollection | null;\n}\n\n/**\n * Het werkblad rechts naast de kaart: kaartbeeld en legenda bovenaan, daaronder\n * Lagen, Vak en Gebied.\n *\n * Naast de kaart en niet erop: de kaart wordt smaller in plaats van dat er geul\n * achter een paneel wegvalt. Ingeklapt blijft een smalle strook staan met de\n * afkorting en de kleurbalk, zodat je ook dan weet wat de kleuren betekenen.\n */\nexport default function Werkblad({ stand, zet, mapRef, datasets, baggervakken }: WerkbladProps) {\n  const { state } = useKaart();\n  const [peil, setPeil] = useState<Peil>(state.referenceLevel === 'lat' ? 'lat' : 'nap');\n\n  // Hier en niet in het tabblad: de uitkomst staat ook op de kaart en moet met\n  // een datumwissel meelopen, welk tabblad er ook open staat.\n  useGebiedStatistiek(datasets);\n\n  // Ook hier en niet in het tabblad: dat verdwijnt bij elke tabwissel, en dan\n  // haalt elke klik in een vak de lijst opnieuw op en staat er tot het antwoord\n  // er is \"nog geen peiling\" bij een vak dat wel gepeild is.\n  const laatstePeilingen = useLatestDatasets(0);\n\n  // De kaart kent zijn eigen breedte pas na resize(); zonder blijft er na het\n  // uitklappen een strook kaart onder het werkblad hangen.\n  const eersteKeer = useRef(true);\n  useEffect(() => {\n    if (eersteKeer.current) {\n      eersteKeer.current = false;\n      return;\n    }\n    mapRef.current?.resize();\n  }, [stand.open, mapRef]);\n\n  if (!stand.open) {\n    const balk = legendaBalk(state.referenceLevel, state.colormap, 'to top');\n    const naam = REFERENTIE_NAAM[state.referenceLevel];\n    // Ook ingeklapt mag de kans nooit voor een meting worden aangezien. De\n    // oranje noot staat alleen in het open kaartbeeld, dus zegt de strook het\n    // hier zelf.\n    const verwachting = heeftColormapKeuze(state.referenceLevel) && state.colormap === 'kans';\n    const verwachtingTekst = `Verwachting, geen meting: kans boven ${REFERENTIE_ZIN[state.referenceLevel]}`;\n    return (\n      <aside\n        aria-label=\"Werkblad (ingeklapt)\"\n        className=\"flex w-14 shrink-0 flex-col items-center gap-1 border-l bg-background py-2\"\n      >\n        <StrookKnop\n          label=\"Werkblad uitklappen\"\n          onClick={() => zet({ open: true })}\n          className=\"mb-2\"\n        >\n          <ChevronsLeft className=\"size-[18px]\" />\n        </StrookKnop>\n        {TABS.map(({ tab, label, Icoon }) => (\n          <StrookKnop key={tab} label={label} onClick={() => zet({ open: true, tab })}>\n            <Icoon className=\"size-[18px]\" />\n          </StrookKnop>\n        ))}\n        <div className=\"flex-1\" />\n        <button\n          type=\"button\"\n          aria-label={\n            verwachting ? `Kaartbeeld en legenda. ${verwachtingTekst}` : 'Kaartbeeld en legenda'\n          }\n          title={verwachting ? verwachtingTekst : 'Kaartbeeld'}\n          onClick={() => zet({ open: true })}\n          className={cn(\n            'mb-2 flex w-11 flex-col items-center gap-1.5 rounded-[9px] border py-2 hover:bg-accent',\n            verwachting ? 'border-verwachting bg-verwachting-vlak' : 'border-transparent'\n          )}\n        >\n          <span\n            className={cn(\n              // \"Verschil\" is te breed voor de strook in het cijferlettertype.\n              naam.length > 4 ? 'text-[10px]' : 'getal text-xs',\n              'font-medium',\n              verwachting ? 'text-verwachting-tekst' : 'text-primary'\n            )}\n          >\n            {naam}\n          </span>\n          {verwachting && <span className=\"text-xs text-verwachting-tekst\">kans</span>}\n          <span className=\"getal text-center text-xs leading-tight text-muted-foreground\">\n            {balk.eind}\n          </span>\n          <span className=\"h-[180px] w-3 rounded-[3px]\" style={{ background: balk.achtergrond }} />\n          <span className=\"getal text-center text-xs leading-tight text-muted-foreground\">\n            {balk.begin}\n          </span>\n        </button>\n      </aside>\n    );\n  }\n\n  const gekozenTab = TABS.findIndex(({ tab }) => tab === stand.tab);\n\n  return (\n    // Onder lg schuift het werkblad over de kaart in plaats van ernaast: naast\n    // 360 px werkblad blijft op een telefoon of staande tablet van de geul\n    // niets of bijna niets over.\n    <aside\n      aria-label=\"Werkblad\"\n      className=\"flex min-h-0 w-[360px] max-w-full shrink-0 flex-col border-l bg-background max-lg:absolute max-lg:inset-y-0 max-lg:right-0 max-lg:z-40 max-lg:shadow-lg\"\n    >\n      <WerkbladKaartbeeld peil={peil} onPeil={setPeil} onInklappen={() => zet({ open: false })} />\n\n      <div\n        role=\"tablist\"\n        aria-label=\"Tabbladen\"\n        className=\"flex h-[52px] shrink-0 items-stretch gap-0.5 border-b px-2\"\n        onKeyDown={(e) => {\n          const i = volgendeIndex(e.key, gekozenTab, TABS.length);\n          if (i == null) return;\n          e.preventDefault();\n          zet({ tab: TABS[i].tab });\n          e.currentTarget.querySelectorAll<HTMLElement>('[role=\"tab\"]')[i]?.focus();\n        }}\n      >\n        {TABS.map(({ tab, label, Icoon }) => {\n          const gekozen = stand.tab === tab;\n          return (\n            <button\n              key={tab}\n              type=\"button\"\n              role=\"tab\"\n              id={`werkblad-tab-${tab}`}\n              aria-selected={gekozen}\n              aria-controls=\"werkblad-inhoud\"\n              tabIndex={gekozen ? 0 : -1}\n              onClick={() => zet({ tab })}\n              className={cn(\n                'flex items-center gap-[7px] border-b-2 px-2.5 text-[13px] transition-colors',\n                gekozen\n                  ? 'border-primary font-semibold text-primary'\n                  : 'border-transparent font-medium text-muted-foreground hover:text-foreground'\n              )}\n            >\n              <Icoon className=\"size-4\" />\n              {tab === 'vak' && state.selectie?.soort === 'stortlocatie'\n                ? state.selectie.code\n                : tab === 'vak' && state.selectedVak\n                  ? `Vak ${state.selectedVak}`\n                  : label}\n            </button>\n          );\n        })}\n      </div>\n\n      <div\n        id=\"werkblad-inhoud\"\n        role=\"tabpanel\"\n        aria-labelledby={`werkblad-tab-${stand.tab}`}\n        className=\"min-h-0 flex-1 overflow-y-auto\"\n      >\n        {stand.tab === 'lagen' && (\n          <WerkbladLagen meer={stand.meerLagen} onMeer={(meerLagen) => zet({ meerLagen })} />\n        )}\n        {stand.tab === 'vak' && (\n          <WerkbladVak\n            baggervakken={baggervakken}\n            laatstePeilingen={laatstePeilingen}\n            opDeKaart={datasets}\n          />\n        )}\n        {stand.tab === 'gebied' && <WerkbladGebied />}\n      </div>\n    </aside>\n  );\n}\n\nfunction StrookKnop({\n  label,\n  onClick,\n  className,\n  children,\n}: {\n  label: string;\n  onClick: () => void;\n  className?: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      title={label}\n      onClick={onClick}\n      className={cn(\n        'flex size-10 items-center justify-center rounded-[9px] text-muted-foreground hover:bg-accent hover:text-foreground',\n        className\n      )}\n    >\n      {children}\n    </button>\n  );\n}\n","import { useCallback, useRef } from 'react';\n\nimport CrossSectionDrawer from './CrossSectionDrawer';\nimport { KaartProvider, useKaart, toontPeilsoort } from './KaartContext';\nimport MapToolbar from './MapToolbar';\nimport MapView from './MapView';\nimport TimelineBar from './TimelineBar';\nimport Werkblad, { useWerkbladStand } from './Werkblad';\n\nimport type { KaartObject } from '@/js/lib/kaart-object';\nimport type { MapRef } from 'react-map-gl/maplibre';\n\nimport { useBaggervakken } from '@/js/hooks/use-baggervakken';\nimport { useAtpDatasets, useReadyDatasets } from '@/js/hooks/use-datasets';\n\nfunction KaartPageInner() {\n  const { state, dispatch } = useKaart();\n  const mapRef = useRef<MapRef | null>(null);\n  const { stand, zet, openOp } = useWerkbladStand();\n\n  const { datasets } = useReadyDatasets(state.selectedDate, state.retentionMonths);\n  const { datasets: atpDatasets } = useAtpDatasets(\n    state.selectedDate,\n    state.retentionMonths,\n    toontPeilsoort(state, 'atp')\n  );\n  const { geojson: baggervakkenGeoJson } = useBaggervakken();\n\n  // Ook bij een tweede klik op hetzelfde vak: wie intussen naar Lagen ging,\n  // verwacht het vak terug te zien. Een klik naast alles wist de keuze, maar\n  // laat het werkblad op het tabblad waar het stond.\n  const handleObjectClick = useCallback(\n    (object: KaartObject | null) => {\n      dispatch({ type: 'SELECT_OBJECT', object });\n      if (object) openOp('vak');\n    },\n    [dispatch, openOp]\n  );\n\n  const handleGebiedStart = useCallback(() => openOp('gebied'), [openOp]);\n\n  return (\n    // Vult het hele venster; -m-4/lg:-m-6 heft de padding van AppShellLayout op.\n    // Op een telefoon gaat de menuregel (h-12) van AppShellLayout eraf.\n    <section className=\"-m-4 flex h-[calc(100dvh-3rem)] min-h-[560px] flex-col overflow-hidden bg-muted/30 md:h-dvh lg:-m-6\">\n      {/* Geen zichtbare kop (de kaart vult het venster), wel een paginatitel. */}\n      <h1 className=\"sr-only\">Kaart</h1>\n\n      {/* Kaart en werkblad naast elkaar, de tijdlijn eronder over de volle\n          breedte. relative: onder lg ligt het werkblad over de kaart, binnen\n          deze rij en dus niet over de tijdlijn. */}\n      <div className=\"relative flex min-h-0 flex-1\">\n        {/* De kaartkolom: de kaart met het gereedschap, en het dwarsprofiel als\n            lade eronder. De lade duwt de kaart omhoog in plaats van erover te\n            liggen, en reikt niet onder het werkblad. */}\n        <div className=\"flex min-w-0 flex-1 flex-col\">\n          <div className=\"relative min-h-0 flex-1 overflow-hidden\">\n            <MapView onObjectClick={handleObjectClick} mapRef={mapRef} />\n            <MapToolbar mapRef={mapRef} onGebiedStart={handleGebiedStart} />\n          </div>\n\n          {state.crossSection.line && (\n            <CrossSectionDrawer\n              datasets={datasets}\n              atpDatasets={atpDatasets}\n              baggervakken={baggervakkenGeoJson}\n              mapRef={mapRef}\n            />\n          )}\n        </div>\n\n        {/* Meetgebied (#115): alleen reguliere peilingen, zie WerkbladGebied. */}\n        <Werkblad\n          stand={stand}\n          zet={zet}\n          mapRef={mapRef}\n          datasets={datasets}\n          baggervakken={baggervakkenGeoJson}\n        />\n      </div>\n\n      <TimelineBar />\n    </section>\n  );\n}\n\nexport default function KaartPage() {\n  return (\n    <KaartProvider>\n      <KaartPageInner />\n    </KaartProvider>\n  );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { PlaatmigratieRun } from '@/js/lib/plaatmigratie';\n\n// Per run en jaar: na een nieuwe berekening zijn de blobs van de vorige weg.\nconst cogUrlCache = new Map<string, { url: string; expiry: number }>();\nexport const COG_URL_TTL_MS = 50 * 60 * 1000; // net onder de SAS-geldigheid\n\n/** De actieve run van de Plaatmigratiemonitor; `leeg` als hij nog niet berekend is. */\nexport function usePlaatmigratie() {\n  const [run, setRun] = useState<PlaatmigratieRun | null>(null);\n  const [laden, setLaden] = useState(true);\n  const [leeg, setLeeg] = useState(false);\n\n  useEffect(() => {\n    let cancelled = false;\n    fetch('/api/plaatmigratie/')\n      .then(async (r) => {\n        if (cancelled) return;\n        if (r.status === 404) {\n          setLeeg(true);\n          return;\n        }\n        if (!r.ok) throw new Error(r.statusText);\n        const data = (await r.json()) as PlaatmigratieRun;\n        if (!cancelled) setRun(data);\n      })\n      .catch(() => {\n        if (!cancelled) setLeeg(true);\n      })\n      .finally(() => {\n        if (!cancelled) setLaden(false);\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, []);\n\n  return { run, laden, leeg };\n}\n\n/** Welk beeld: het model (tussenstanden en voorspelling) of alleen de ruwe meting. */\nexport type PlaatmigratieSoort = 'model' | 'meting';\n\n/** Tijdelijke leeslink naar de COG van een jaar, gecachet tot vlak voor hij verloopt. */\nexport async function getPlaatmigratieCogUrl(\n  runId: number,\n  jaar: number,\n  soort: PlaatmigratieSoort = 'model'\n): Promise<string | null> {\n  const sleutel = `${runId}-${soort}-${jaar}`;\n  const cached = cogUrlCache.get(sleutel);\n  if (cached && cached.expiry > Date.now()) return cached.url;\n  try {\n    const query = soort === 'meting' ? '?soort=meting' : '';\n    const resp = await fetch(`/api/plaatmigratie/${jaar}/cog-url/${query}`);\n    if (!resp.ok) return null;\n    const url = (await resp.json()).url as string;\n    cogUrlCache.set(sleutel, { url, expiry: Date.now() + COG_URL_TTL_MS });\n    return url;\n  } catch {\n    return null;\n  }\n}\n\nexport interface PlaatmigratieFilm {\n  url: string;\n  titel: string;\n}\n\n/** Leeslink naar de nieuwste plaatfilm, of null als er (nog) geen is. */\nexport async function getPlaatmigratieFilm(): Promise<PlaatmigratieFilm | null> {\n  try {\n    const resp = await fetch('/api/plaatmigratie/film/');\n    if (!resp.ok) return null;\n    return (await resp.json()) as PlaatmigratieFilm;\n  } catch {\n    return null;\n  }\n}\n\n/** Of er een plaatfilm is (één keer bij het openen van de pagina). */\nexport function usePlaatmigratieFilm() {\n  const [film, setFilm] = useState<PlaatmigratieFilm | null>(null);\n  useEffect(() => {\n    let cancelled = false;\n    getPlaatmigratieFilm().then((f) => {\n      if (!cancelled) setFilm(f);\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, []);\n  return film;\n}\n","import { X } from 'lucide-react';\nimport { useEffect, useState } from 'react';\n\nimport { Button } from '@/js/components/ui/button';\nimport { getPlaatmigratieFilm } from '@/js/hooks/use-plaatmigratie';\n\ninterface Props {\n  titel: string;\n  onSluit: () => void;\n}\n\n/** De plaatfilm over de kaart heen. Haalt bij openen een verse leeslink op (de SAS verloopt). */\nexport default function FilmVenster({ titel, onSluit }: Props) {\n  const [url, setUrl] = useState<string | null>(null);\n  const [fout, setFout] = useState(false);\n\n  useEffect(() => {\n    let cancelled = false;\n    getPlaatmigratieFilm().then((f) => {\n      if (cancelled) return;\n      if (f) setUrl(f.url);\n      else setFout(true);\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, []);\n\n  useEffect(() => {\n    const opToets = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') onSluit();\n    };\n    window.addEventListener('keydown', opToets);\n    return () => window.removeEventListener('keydown', opToets);\n  }, [onSluit]);\n\n  return (\n    <div\n      className=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-black/85 p-6\"\n      role=\"dialog\"\n      aria-label={titel}\n    >\n      <div className=\"flex w-full max-w-[1600px] items-center justify-between text-white\">\n        <span className=\"text-sm font-medium\">{titel}</span>\n        <Button\n          size=\"icon\"\n          variant=\"ghost\"\n          className=\"text-white hover:bg-white/10\"\n          onClick={onSluit}\n        >\n          <X className=\"size-5\" />\n          <span className=\"sr-only\">Sluiten</span>\n        </Button>\n      </div>\n      {url && (\n        <video\n          src={url}\n          className=\"max-h-[calc(100%-3rem)] w-full max-w-[1600px] rounded-md\"\n          controls\n          autoPlay\n          loop\n          muted\n          playsInline\n        />\n      )}\n      {!url && !fout && <span className=\"text-sm text-white/70\">Film laden…</span>}\n      {fout && <span className=\"text-sm text-white/70\">De film is niet beschikbaar.</span>}\n    </div>\n  );\n}\n","import { Loader2, Pause, Play } from 'lucide-react';\nimport { useEffect } from 'react';\n\nimport { Button } from '@/js/components/ui/button';\nimport { Label } from '@/js/components/ui/label';\nimport { Slider } from '@/js/components/ui/slider';\nimport { Switch } from '@/js/components/ui/switch';\n\nconst AFSPEEL_MS = 700;\n\ninterface Props {\n  /** De jaren op de schuif, oplopend. */\n  jaren: number[];\n  jaar: number;\n  meetjaren: number[];\n  voorspelling: [number, number] | null;\n  metVoorspelling: boolean;\n  speelt: boolean;\n  /** Afspelen wacht tot alle jaren voor het beeld geladen zijn (de stand toont de pagina). */\n  laden: boolean;\n  onJaar: (jaar: number) => void;\n  onSpeelt: (speelt: boolean) => void;\n  onMetVoorspelling: (aan: boolean) => void;\n}\n\n/**\n * Schuif over de jaren met oranje streepjes voor metingen en een oranje vlak\n * voor de voorspelling. De schuif loopt per stap door de lijst jaren, zodat\n * hij zonder voorspelling van meting naar meting springt.\n */\nexport default function Tijdbalk({\n  jaren,\n  jaar,\n  meetjaren,\n  voorspelling,\n  metVoorspelling,\n  speelt,\n  laden,\n  onJaar,\n  onSpeelt,\n  onMetVoorspelling,\n}: Props) {\n  const index = Math.max(0, jaren.indexOf(jaar));\n  const laatste = jaren.length - 1;\n  const pos = (j: number) => `${(100 * Math.max(0, jaren.indexOf(j))) / Math.max(1, laatste)}%`;\n  const meetOpSchuif = meetjaren.filter((j) => jaren.includes(j));\n\n  useEffect(() => {\n    if (!speelt || laden) return;\n    const t = setInterval(() => onJaar(jaren[index >= laatste ? 0 : index + 1]), AFSPEEL_MS);\n    return () => clearInterval(t);\n  }, [speelt, laden, index, laatste, jaren, onJaar]);\n\n  return (\n    <div className=\"flex items-center gap-3 rounded-xl border bg-card/95 px-4 py-3 shadow-sm\">\n      <Button\n        size=\"icon\"\n        variant=\"outline\"\n        aria-label={speelt ? 'Pauzeren' : 'Afspelen'}\n        onClick={() => onSpeelt(!speelt)}\n      >\n        {speelt && laden ? (\n          <Loader2 className=\"size-4 animate-spin\" />\n        ) : speelt ? (\n          <Pause className=\"size-4\" />\n        ) : (\n          <Play className=\"size-4\" />\n        )}\n      </Button>\n      <div className=\"getal w-12 text-center text-lg font-medium\">{jaar}</div>\n      <div className=\"relative flex-1 pt-3 pb-5\">\n        {metVoorspelling && voorspelling && jaren.includes(voorspelling[0]) && (\n          <div\n            className=\"absolute top-0 h-2 rounded-sm bg-orange-500/25\"\n            style={{\n              left: pos(voorspelling[0]),\n              width: `calc(${pos(voorspelling[1])} - ${pos(voorspelling[0])})`,\n            }}\n            title=\"voorspelling\"\n          />\n        )}\n        {meetOpSchuif.map((j) => (\n          <div\n            key={j}\n            className=\"absolute top-0 h-2 w-0.5 bg-orange-600\"\n            style={{ left: pos(j) }}\n            title={`meting ${j}`}\n          />\n        ))}\n        <Slider\n          min={0}\n          max={laatste}\n          step={1}\n          value={[index]}\n          onValueChange={([i]) => onJaar(jaren[i])}\n          aria-label=\"Jaar\"\n        />\n        {meetOpSchuif.map((j) => (\n          <span\n            key={j}\n            className=\"getal absolute bottom-0 -translate-x-1/2 text-xs text-muted-foreground\"\n            style={{ left: pos(j) }}\n          >\n            {j}\n          </span>\n        ))}\n      </div>\n      <div className=\"flex items-center gap-2\">\n        <Switch\n          id=\"pm-voorspelling\"\n          checked={metVoorspelling}\n          onCheckedChange={onMetVoorspelling}\n        />\n        <Label htmlFor=\"pm-voorspelling\" className=\"text-sm whitespace-nowrap\">\n          Toon voorspelling\n        </Label>\n      </div>\n    </div>\n  );\n}\n","/**\n * De Plaatmigratiemonitor: typen van de API, de dieptekleur en de teksten.\n *\n * Los van de pagina zodat de kaartpagina dezelfde laag en dezelfde kleur kan\n * gebruiken als ondergrond.\n */\n\nexport type GebiedSoort =\n  | 'meting'\n  | 'tussenstand'\n  | 'voorspelling'\n  | 'laatste_meting'\n  | 'geen_meting';\n\nexport interface GebiedStatus {\n  gebied: string;\n  vakken: string[];\n  soort: GebiedSoort;\n  tekst: string;\n}\n\nexport interface PlaatmigratieJaar {\n  jaar: number;\n  status: GebiedStatus[];\n  /** Of er in dit jaar een ruwe meting is (alleen de cellen die toen gemeten zijn). */\n  heeft_meting: boolean;\n}\n\nexport interface Meetgebied {\n  naam: string;\n  vakken: string[];\n  meetjaren: number[];\n  laatste_meting: number;\n  voorspeld: boolean;\n}\n\nexport interface Terugtoets {\n  gebied: string;\n  doel: number;\n  rmse_persistentie: number;\n  rmse_doorgetrokken: number;\n}\n\nexport interface PlaatmigratieRun {\n  id: number;\n  berekend_op: string;\n  horizon: number;\n  meetgebieden: Meetgebied[];\n  terugtoets: Terugtoets[];\n  bbox: number[] | null;\n  jaren: PlaatmigratieJaar[];\n}\n\nexport const PLAATMIGRATIE_MIN = -16;\nexport const PLAATMIGRATIE_MAX = 0;\n\n// Diep blauw naar ondiep zand; dieper dan -16 m doet er voor de plaat niet toe.\nconst STOPS: [number, [number, number, number]][] = [\n  [0.0, [11, 37, 69]],\n  [0.35, [19, 86, 138]],\n  [0.6, [79, 159, 198]],\n  [0.78, [185, 219, 230]],\n  [0.88, [232, 217, 168]],\n  [1.0, [201, 163, 90]],\n];\n\nexport function plaatmigratieKleur(diepte: number): [number, number, number] {\n  const t = Math.min(\n    1,\n    Math.max(0, (diepte - PLAATMIGRATIE_MIN) / (PLAATMIGRATIE_MAX - PLAATMIGRATIE_MIN))\n  );\n  for (let i = 0; i < STOPS.length - 1; i++) {\n    const [t0, k0] = STOPS[i];\n    const [t1, k1] = STOPS[i + 1];\n    if (t <= t1) {\n      const s = (t - t0) / (t1 - t0);\n      return [\n        Math.round(k0[0] + s * (k1[0] - k0[0])),\n        Math.round(k0[1] + s * (k1[1] - k0[1])),\n        Math.round(k0[2] + s * (k1[2] - k0[2])),\n      ];\n    }\n  }\n  return STOPS[STOPS.length - 1][1];\n}\n\n/** Kleurfunctie voor het `cog://`-protocol: NaN (niet gemeten) is transparant. */\nexport function plaatmigratieKleurFunctie(\n  pixel: ArrayLike<number>,\n  kleur: Uint8ClampedArray\n): void {\n  const diepte = pixel[0];\n  if (!Number.isFinite(diepte)) {\n    kleur[3] = 0;\n    return;\n  }\n  const [r, g, b] = plaatmigratieKleur(diepte);\n  kleur[0] = r;\n  kleur[1] = g;\n  kleur[2] = b;\n  kleur[3] = 255;\n}\n\n/**\n * Eén regel per reeks aangrenzende vakgroepen met dezelfde tekst, in vakvolgorde.\n *\n * Groepen verschillen soms alleen in oude meetjaren (A1, A2, A3 apart), terwijl\n * ze in de meeste jaren hetzelfde zeggen; dan zou het kader acht keer dezelfde\n * regel tonen. Gebieden zonder vakken zijn omgeving en krijgen geen regel.\n */\nexport function statusRegels(status: GebiedStatus[]): { tekst: string; voorspelling: boolean }[] {\n  const reeksen: GebiedStatus[][] = [];\n  for (const s of status) {\n    if (s.vakken.length === 0) continue;\n    const vorige = reeksen[reeksen.length - 1];\n    if (vorige && vorige[0].tekst === s.tekst) vorige.push(s);\n    else reeksen.push([s]);\n  }\n  return reeksen.map((r) => {\n    // Eén groep houdt zijn eigen naam; een reeks heet naar eerste en laatste vak.\n    const naam =\n      r.length === 1\n        ? r[0].gebied\n        : `${r[0].vakken[0]}–${r[r.length - 1].vakken[r[r.length - 1].vakken.length - 1]}`;\n    return { tekst: `${naam}: ${r[0].tekst}`, voorspelling: r[0].soort === 'voorspelling' };\n  });\n}\n\nexport function meetjarenMetVakken(run: PlaatmigratieRun): number[] {\n  const jaren = new Set<number>();\n  for (const g of run.meetgebieden) {\n    if (g.vakken.length > 0) g.meetjaren.forEach((j) => jaren.add(j));\n  }\n  return [...jaren].sort((a, b) => a - b);\n}\n\nexport function voorspellingsVlak(run: PlaatmigratieRun): [number, number] | null {\n  const voorspeld = run.meetgebieden.filter((g) => g.voorspeld);\n  if (voorspeld.length === 0) return null;\n  const laatste = Math.max(...voorspeld.map((g) => g.laatste_meting));\n  return [laatste, laatste + run.horizon];\n}\n\n/** Hoeveel procent kleiner de fout was dan \"de bodem blijft liggen\", op de laatste meting. */\nexport function winstLaatsteToets(run: PlaatmigratieRun): number | null {\n  const gebied = run.meetgebieden.find((g) => g.voorspeld);\n  if (!gebied) return null;\n  const toets = run.terugtoets.find(\n    (t) => t.gebied === gebied.naam && t.doel === gebied.laatste_meting\n  );\n  if (!toets || toets.rmse_persistentie <= 0) return null;\n  return Math.round(100 * (1 - toets.rmse_doorgetrokken / toets.rmse_persistentie));\n}\n\n/**\n * Nieuwe COG-links bij de bestaande zetten. Komt er niets bij, dan dezelfde\n * map terug: een nieuwe map zonder nieuwe inhoud zou het laad-effect opnieuw\n * starten en een mislukte link eindeloos opnieuw laten ophalen.\n */\nexport function voegUrlsToe(\n  oud: Map<number, string>,\n  paren: ReadonlyArray<readonly [number, string | null]>\n): Map<number, string> {\n  const nieuweParen = paren.filter(\n    (p): p is readonly [number, string] => p[1] !== null && oud.get(p[0]) !== p[1]\n  );\n  if (nieuweParen.length === 0) return oud;\n  const nieuw = new Map(oud);\n  for (const [j, url] of nieuweParen) nieuw.set(j, url);\n  return nieuw;\n}\n\n/**\n * De jaren op de schuif. Met voorspelling: elk jaar van de run, inclusief\n * tussenstanden en voorspelling. Zonder: alleen de echte meetjaren.\n */\nexport function zichtbareJaren(run: PlaatmigratieRun, metVoorspelling: boolean): number[] {\n  if (metVoorspelling) return run.jaren.map((j) => j.jaar);\n  return meetjaren(run);\n}\n\n/** De jaren met een ruwe meting: de streepjes op de tijdbalk. */\nexport function meetjaren(run: PlaatmigratieRun): number[] {\n  return run.jaren.filter((j) => j.heeft_meting).map((j) => j.jaar);\n}\n\n/** Het jaar uit `jaren` dat het dichtst bij `jaar` ligt; bij gelijke afstand het vroegste. */\nexport function dichtstbijzijndeJaar(jaren: number[], jaar: number): number {\n  return jaren.reduce((beste, j) => (Math.abs(j - jaar) < Math.abs(beste - jaar) ? j : beste));\n}\n","/**\n * De bodem van de Plaatmigratiemonitor als rasterlagen, één per jaar.\n *\n * Invoer is alleen het jaar, zodat de kaartpagina hem later als ondergrond\n * kan gebruiken met het jaar uit de datumslider. Jaren worden gewisseld via\n * `raster-opacity` en blijven gemount, zoals de maandlagen op de kaart: een\n * nieuwe source per stap zou de tegels opnieuw laden en wit flitsen.\n *\n * Het `cog://`-protocol wordt op moduleniveau in `pages/kaart/MapView.tsx`\n * geregistreerd; `routes/index.ts` importeert de kaartpagina direct, dus het\n * staat er al. Hier niet opnieuw registreren: dat zou de omwikkelde versie\n * van de kaartpagina overschrijven.\n *\n * Afspelen (`alleJaren`): alle jaren gemount, zodat elke stap een dekkingswissel\n * is. Met per stap een nieuwe bron liep de schuif (700 ms) altijd voor het laden\n * uit: 44 jaren van elk ~8 MB float32 die de browser zelf decodeert. `onKlaar`\n * meldt op een hartslag hoeveel jaren voor het huidige beeld geladen zijn, niet\n * op `idle`: dat vuurt niet meer op een kaart die al stilstaat.\n *\n * De jaren komen er in afspeelvolgorde bij, een paar per tik en alleen als alles\n * op de kaart geladen is. MapLibre laat hoogstens 16 beeldverzoeken tegelijk\n * lopen, ook voor `cog://`; alle 44 jaren tegelijk zette de Google-ondergrond\n * achteraan in die rij en de satelliet bleef weg.\n */\nimport { setColorFunction } from '@geomatico/maplibre-cog-protocol';\nimport { useEffect, useRef, useState } from 'react';\nimport { Layer, Source, useMap } from 'react-map-gl/maplibre';\n\nimport {\n  COG_URL_TTL_MS,\n  getPlaatmigratieCogUrl,\n  type PlaatmigratieSoort,\n} from '@/js/hooks/use-plaatmigratie';\nimport { plaatmigratieKleurFunctie, voegUrlsToe } from '@/js/lib/plaatmigratie';\nimport { startMeting } from '@/js/lib/tijdlijn-meting';\n\n/**\n * Alleen het gekozen jaar en zijn buren in de lijst staan op de kaart. Elke\n * gemounte laag laadt tegels mee, ook op dekking 0; met alle bezochte jaren\n * gemount werd het laden traag. De buren maken een stap op de schuif direct.\n */\nconst BUREN = 1;\n/** Hoe vaak bij afspelen geteld wordt hoeveel jaren geladen zijn. */\nconst KLAAR_TIK_MS = 200;\n/** Zoveel jaren per tik erbij bij afspelen, als de kaart verder klaar is. */\nconst BIJ_PER_TIK = 2;\n\nconst GEEN_URLS = new Map<number, string>();\n\nfunction inVenster(jaren: number[], jaar: number, j: number): boolean {\n  return Math.abs(jaren.indexOf(j) - jaren.indexOf(jaar)) <= BUREN && jaren.includes(j);\n}\n\n/** De jaren na `jaar` in afspeelvolgorde, rond naar het begin. */\nfunction afspeelvolgorde(jaren: number[], jaar: number): number[] {\n  const i = Math.max(0, jaren.indexOf(jaar));\n  return [...jaren.slice(i + 1), ...jaren.slice(0, i)];\n}\n\nfunction bronId(soort: PlaatmigratieSoort, jaar: number): string {\n  return `plaatmigratie-${soort}-${jaar}`;\n}\n\ninterface Props {\n  runId: number;\n  /** Model (tussenstanden en voorspelling) of alleen de ruwe meting. */\n  soort?: PlaatmigratieSoort;\n  jaren: number[];\n  jaar: number;\n  beforeId?: string;\n  /** Afspelen: alle jaren op de kaart in plaats van het jaar en zijn buren. */\n  alleJaren?: boolean;\n  /** Bij `alleJaren`: hoeveel jaren voor het huidige beeld geladen (of mislukt) zijn. */\n  onKlaar?: (aantal: number) => void;\n}\n\nexport default function PlaatmigratieLagen({\n  runId,\n  soort = 'model',\n  jaren,\n  jaar,\n  beforeId,\n  alleJaren = false,\n  onKlaar,\n}: Props) {\n  const { current: kaart } = useMap();\n  // Links per soort: na een wissel van model naar meting mag een modellink\n  // nooit onder een meting-id op de kaart komen.\n  const [staat, setStaat] = useState<{ soort: PlaatmigratieSoort; urls: Map<number, string> }>(\n    () => ({ soort, urls: new Map() })\n  );\n  const urls = staat.soort === soort ? staat.urls : GEEN_URLS;\n  // Jaren waarvan de link niet op te halen was; niet opnieuw proberen tot de\n  // volgende verversing, anders blijft een fout eindeloos doorlopen.\n  const mislukt = useRef(new Set<number>());\n  // Bij afspelen: de jaren die er buiten het venster bij gezet zijn.\n  const [bij, setBij] = useState<Set<number>>(() => new Set());\n  const urlsRef = useRef(urls);\n  urlsRef.current = urls;\n  const jaarRef = useRef(jaar);\n  jaarRef.current = jaar;\n  const gemount = (j: number) => jaren.includes(j) && (inVenster(jaren, jaar, j) || bij.has(j));\n\n  useEffect(() => {\n    if (!alleJaren) setBij(new Set());\n  }, [alleJaren]);\n\n  // SAS-links verlopen. Begin na de cachetijd met verse links; ook als er een\n  // nieuwe run is, want de blobs van de vorige zijn dan weg.\n  useEffect(() => {\n    setStaat({ soort, urls: new Map() });\n    mislukt.current.clear();\n    const t = setInterval(() => {\n      setStaat({ soort, urls: new Map() });\n      mislukt.current.clear();\n    }, COG_URL_TTL_MS);\n    return () => clearInterval(t);\n  }, [runId, soort]);\n\n  useEffect(() => {\n    const gewenst = jaren.filter((j) => gemount(j) && !urls.has(j) && !mislukt.current.has(j));\n    if (gewenst.length === 0) return;\n    let cancelled = false;\n    Promise.all(\n      gewenst.map(async (j) => [j, await getPlaatmigratieCogUrl(runId, j, soort)] as const)\n    ).then((paren) => {\n      if (cancelled) return;\n      for (const [j, url] of paren) {\n        if (url) setColorFunction(url, plaatmigratieKleurFunctie);\n        else mislukt.current.add(j);\n      }\n      setStaat((oud) => {\n        if (oud.soort !== soort) return oud;\n        const nieuw = voegUrlsToe(oud.urls, paren);\n        return nieuw === oud.urls ? oud : { soort, urls: nieuw };\n      });\n    });\n    return () => {\n      cancelled = true;\n    };\n  }, [runId, soort, jaren, jaar, urls, bij]);\n\n  useEffect(() => {\n    if (!alleJaren || !onKlaar) return;\n    const stop = startMeting('plaatmigratie.voorladen', { jaren: jaren.length });\n    let gemeten = false;\n    const tik = () => {\n      const map = kaart?.getMap();\n      if (!map) return;\n      // Een jaar zonder leeslink komt er niet; dat mag het afspelen niet ophouden.\n      const aantal = jaren.filter((j) => {\n        if (mislukt.current.has(j)) return true;\n        const id = bronId(soort, j);\n        return !!map.getSource(id) && map.isSourceLoaded(id);\n      }).length;\n      onKlaar(aantal);\n      if (!gemeten && aantal === jaren.length) {\n        gemeten = true;\n        stop();\n      }\n      // Pas bijzetten als alles op de kaart er is, ondergrond inbegrepen, en de\n      // vorige jaren al een bron hebben (de leeslink komt asynchroon).\n      setBij((oud) => {\n        const vensterOfBij = (j: number) => inVenster(jaren, jaarRef.current, j) || oud.has(j);\n        const wacht = jaren.some(\n          (j) => vensterOfBij(j) && !urlsRef.current.has(j) && !mislukt.current.has(j)\n        );\n        if (wacht || !map.areTilesLoaded()) return oud;\n        const volgende = afspeelvolgorde(jaren, jaarRef.current)\n          .filter((j) => !vensterOfBij(j))\n          .slice(0, BIJ_PER_TIK);\n        return volgende.length === 0 ? oud : new Set([...oud, ...volgende]);\n      });\n    };\n    tik();\n    const t = setInterval(tik, KLAAR_TIK_MS);\n    return () => {\n      clearInterval(t);\n      if (!gemeten) stop('cancelled');\n    };\n  }, [alleJaren, onKlaar, kaart, jaren, soort]);\n\n  return (\n    <>\n      {[...urls.entries()]\n        .filter(([j]) => gemount(j))\n        .map(([j, url]) => (\n          <Source\n            // De soort hoort in de key: react-map-gl staat niet toe dat een\n            // gemounte bron van id wisselt (\"source id changed\").\n            key={`${soort}-${j}`}\n            id={bronId(soort, j)}\n            type=\"raster\"\n            url={`cog://${url}#plaatmigratie_${soort}_${j}`}\n            tileSize={256}\n          >\n            <Layer\n              id={`plaatmigratie-laag-${soort}-${j}`}\n              type=\"raster\"\n              beforeId={beforeId}\n              paint={{\n                'raster-opacity': j === jaar ? 1 : 0,\n                'raster-opacity-transition': { duration: 0 },\n                'raster-fade-duration': 0,\n              }}\n            />\n          </Source>\n        ))}\n    </>\n  );\n}\n","import { locationValues } from '@geomatico/maplibre-cog-protocol';\nimport { Film, Loader2 } from 'lucide-react';\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport Map, { Layer, Source } from 'react-map-gl/maplibre';\n\nimport FilmVenster from './FilmVenster';\nimport Tijdbalk from './Tijdbalk';\n\nimport type { MapLayerMouseEvent } from 'react-map-gl/maplibre';\n\nimport PlaatmigratieLagen from '@/js/components/kaart/PlaatmigratieLagen';\nimport { Button } from '@/js/components/ui/button';\nimport { useBaggervakken } from '@/js/hooks/use-baggervakken';\nimport {\n  getPlaatmigratieCogUrl,\n  usePlaatmigratie,\n  usePlaatmigratieFilm,\n} from '@/js/hooks/use-plaatmigratie';\nimport { ondergrondStijl, useGoogleOndergrond } from '@/js/lib/kaart-ondergrond';\nimport {\n  dichtstbijzijndeJaar,\n  meetjaren as meetjarenVan,\n  voorspellingsVlak,\n  zichtbareJaren,\n} from '@/js/lib/plaatmigratie';\n\nconst BEGIN = { latitude: 53.55, longitude: 6.65, zoom: 10.3 };\n/** Hoe vaak er hoogstens een COG-pixel onder de muis gelezen wordt. */\nconst LEESINTERVAL_MS = 150;\nconst GEEN_VAKKEN: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: [] };\n/** De jaarbeelden komen onder deze laag, zodat de vakken altijd zichtbaar blijven. */\nconst VAKKEN_LAAG = 'pm-vakken-lijn';\n\nexport default function PlaatmigratiePage() {\n  const { run, laden, leeg } = usePlaatmigratie();\n  const { geojson: baggervakken } = useBaggervakken();\n  const film = usePlaatmigratieFilm();\n  const [filmOpen, setFilmOpen] = useState(false);\n  const ondergrond = useGoogleOndergrond('satellite');\n  const stijl = useMemo(() => ondergrondStijl(ondergrond), [ondergrond]);\n  const [metVoorspelling, setMetVoorspelling] = useState(true);\n  const jaren = useMemo(\n    () => (run ? zichtbareJaren(run, metVoorspelling) : []),\n    [run, metVoorspelling]\n  );\n  const meetjaren = useMemo(() => (run ? meetjarenVan(run) : []), [run]);\n  // Voorspelling uit: per meetjaar alleen de cellen die toen echt gemeten zijn.\n  const soort = metVoorspelling ? 'model' : 'meting';\n  const [jaarKeuze, setJaarKeuze] = useState<number | null>(null);\n  const [speelt, setSpeelt] = useState(false);\n  // Bij afspelen: hoeveel jaren voor het huidige beeld geladen zijn.\n  const [klaar, setKlaar] = useState(0);\n  const startStop = useCallback((aan: boolean) => {\n    // Een oude telling mag het afspelen niet laten beginnen voordat er geteld is.\n    if (aan) setKlaar(0);\n    setSpeelt(aan);\n  }, []);\n  const [diepte, setDiepte] = useState<number | null>(null);\n  const laatsteRead = useRef(0);\n\n  // Standaard het jaar van de laatste meting: het meest recente echte beeld.\n  // Zonder voorspelling staan alleen meetjaren op de schuif: spring naar de dichtstbijzijnde.\n  const voorladen = speelt && klaar < jaren.length;\n  const gewenst = jaarKeuze ?? meetjaren[meetjaren.length - 1] ?? jaren[jaren.length - 1] ?? 0;\n  const jaar = jaren.length > 0 ? dichtstbijzijndeJaar(jaren, gewenst) : gewenst;\n\n  const onMove = useCallback(\n    async (e: MapLayerMouseEvent) => {\n      const nu = Date.now();\n      if (nu - laatsteRead.current < LEESINTERVAL_MS) return;\n      laatsteRead.current = nu;\n      if (!run) return;\n      const url = await getPlaatmigratieCogUrl(run.id, jaar, soort);\n      if (!url) return;\n      try {\n        const w = await locationValues(\n          url,\n          { latitude: e.lngLat.lat, longitude: e.lngLat.lng },\n          e.target.getZoom()\n        );\n        setDiepte(w.length > 0 && Number.isFinite(w[0]) ? w[0] : null);\n      } catch {\n        setDiepte(null);\n      }\n    },\n    [jaar, run, soort]\n  );\n\n  if (laden) {\n    return <div className=\"p-6 text-sm text-muted-foreground\">Omgevingsmonitor laden…</div>;\n  }\n  if (leeg || !run) {\n    return (\n      <div className=\"p-6 text-sm text-muted-foreground\">\n        De Omgevingsmonitor is nog niet berekend.\n      </div>\n    );\n  }\n\n  return (\n    // Vult het hele venster; -m-4/lg:-m-6 heft de padding van AppShellLayout op.\n    // Op een telefoon gaat de menuregel (h-12) van AppShellLayout eraf.\n    <section className=\"relative -m-4 h-[calc(100dvh-3rem)] min-h-[560px] md:h-dvh overflow-hidden lg:-m-6\">\n      {/* Geen zichtbare kop (de kaart vult het venster), wel een paginatitel. */}\n      <h1 className=\"sr-only\">Omgevingsmonitor</h1>\n      <Map\n        initialViewState={BEGIN}\n        mapStyle={stijl}\n        onMouseMove={onMove}\n        onMouseLeave={() => setDiepte(null)}\n      >\n        {/* De vakken staan er altijd (desnoods leeg), zodat de jaarbeelden er\n              via beforeId onder kunnen; anders komen ze als laatste erbovenop. */}\n        <Source id=\"pm-vakken\" type=\"geojson\" data={baggervakken ?? GEEN_VAKKEN}>\n          <Layer\n            id={VAKKEN_LAAG}\n            type=\"line\"\n            paint={{ 'line-color': '#ea580c', 'line-width': 1.6 }}\n          />\n          <Layer\n            id=\"pm-vakken-label\"\n            type=\"symbol\"\n            layout={{ 'text-field': ['get', 'code'], 'text-size': 12 }}\n            paint={{\n              'text-color': '#ea580c',\n              'text-halo-color': '#ffffff',\n              'text-halo-width': 1,\n            }}\n          />\n        </Source>\n        <PlaatmigratieLagen\n          runId={run.id}\n          soort={soort}\n          jaren={jaren}\n          jaar={jaar}\n          beforeId={VAKKEN_LAAG}\n          alleJaren={speelt}\n          onKlaar={setKlaar}\n        />\n      </Map>\n      {diepte !== null && (\n        <div className=\"absolute top-3 left-3 rounded-md bg-black/55 px-2 py-1 text-xs text-white\">\n          {diepte.toFixed(1).replace('.', ',')} m NAP\n        </div>\n      )}\n      {voorladen && (\n        // Klikken gaat erdoorheen: schuiven en zoomen mag tijdens het laden.\n        <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n          <div\n            role=\"status\"\n            className=\"flex w-80 flex-col items-center gap-3 rounded-xl border bg-card/95 px-8 py-6 shadow-lg\"\n          >\n            <Loader2 className=\"size-10 animate-spin text-muted-foreground\" />\n            <div className=\"text-xl font-semibold\">Jaren laden…</div>\n            <div className=\"text-sm text-muted-foreground tabular-nums\">\n              {klaar} van {jaren.length} jaren in beeld geladen\n            </div>\n            <div className=\"h-2 w-full overflow-hidden rounded-full bg-muted\">\n              <div\n                className=\"h-full bg-primary transition-[width]\"\n                style={{ width: `${(100 * klaar) / Math.max(1, jaren.length)}%` }}\n              />\n            </div>\n            <div className=\"text-xs text-muted-foreground\">Daarna speelt de film vloeiend af.</div>\n          </div>\n        </div>\n      )}\n      {film && (\n        <Button\n          size=\"sm\"\n          variant=\"secondary\"\n          className=\"absolute top-3 right-3 shadow-md\"\n          onClick={() => setFilmOpen(true)}\n        >\n          <Film className=\"size-4\" />\n          Film 2010–2025\n        </Button>\n      )}\n      {film && filmOpen && <FilmVenster titel={film.titel} onSluit={() => setFilmOpen(false)} />}\n      <div className=\"absolute right-4 bottom-4 left-4\">\n        <Tijdbalk\n          jaren={jaren}\n          jaar={jaar}\n          meetjaren={meetjaren}\n          voorspelling={voorspellingsVlak(run)}\n          speelt={speelt}\n          laden={voorladen}\n          metVoorspelling={metVoorspelling}\n          onJaar={setJaarKeuze}\n          onSpeelt={startStop}\n          onMetVoorspelling={setMetVoorspelling}\n        />\n      </div>\n    </section>\n  );\n}\n","import { createBrowserRouter, redirect } from 'react-router';\n\nimport { ProtectedLayout } from '@/js/components';\nimport AnalyseRoute from '@/js/components/AnalyseRoute';\nimport AppShellLayout from '@/js/components/AppShellLayout';\nimport ZandstromenRoute from '@/js/components/ZandstromenRoute';\nimport DataDatasetDetail from '@/js/pages/DataDatasetDetail';\nimport DataDatasets from '@/js/pages/DataDatasets';\nimport DataJobs from '@/js/pages/DataJobs';\nimport DataTrash from '@/js/pages/DataTrash';\nimport DataUploads from '@/js/pages/DataUploads';\nimport Fase2ComingSoon from '@/js/pages/Fase2ComingSoon';\nimport Kaart from '@/js/pages/Kaart';\nimport Login from '@/js/pages/Login';\nimport Plaatmigratie from '@/js/pages/Plaatmigratie';\n\nconst router = createBrowserRouter([\n  { path: '/', loader: () => redirect('/app/kaart') },\n  { path: 'login', Component: Login },\n  {\n    Component: ProtectedLayout,\n    children: [\n      {\n        path: 'app',\n        Component: AppShellLayout,\n        children: [\n          { index: true, loader: () => redirect('/app/kaart') },\n          { path: 'kaart', Component: Kaart },\n          { path: 'plaatmigratie', Component: Plaatmigratie },\n          { path: 'analyse', Component: AnalyseRoute },\n          { path: 'zandstromen', Component: ZandstromenRoute },\n          { path: 'data/uploads', Component: DataUploads },\n          { path: 'data/datasets', Component: DataDatasets },\n          { path: 'data/datasets/:id', Component: DataDatasetDetail },\n          { path: 'data/jobs', Component: DataJobs },\n          { path: 'data/trash', Component: DataTrash },\n          { path: 'fase2/verspreidingslocaties', Component: Fase2ComingSoon },\n          { path: 'fase2/scheepvaart-simulatie', Component: Fase2ComingSoon },\n          { path: 'fase2/predictive', Component: Fase2ComingSoon },\n        ],\n      },\n    ],\n  },\n]);\n\nexport default router;\n","import { useEffect, useState } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router';\n\nimport type { FormEvent } from 'react';\n\nimport loginBg from '@/images/login-bg.jpg';\nimport logo from '@/images/logo-archx-600.png';\nimport { useAuth } from '@/js/auth';\nimport { Button } from '@/js/components/ui/button';\nimport { Card, CardContent } from '@/js/components/ui/card';\nimport { Input } from '@/js/components/ui/input';\nimport { Label } from '@/js/components/ui/label';\nimport { cn } from '@/js/lib/utils';\n\nconst Login = () => {\n  const { login, user, isLoading } = useAuth();\n  const navigate = useNavigate();\n  const [searchParams] = useSearchParams();\n\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [error, setError] = useState<string | null>(null);\n  const [isSubmitting, setIsSubmitting] = useState(false);\n\n  useEffect(() => {\n    if (isLoading) {\n      return;\n    }\n    if (user) {\n      const next = searchParams.get('next') || '/app/kaart';\n      navigate(next, { replace: true });\n    }\n  }, [isLoading, navigate, searchParams, user]);\n\n  const handleSubmit = async (e: FormEvent) => {\n    e.preventDefault();\n    if (isLoading || isSubmitting) {\n      return;\n    }\n    setError(null);\n    setIsSubmitting(true);\n\n    try {\n      await login(email, password);\n      const next = searchParams.get('next') || '/app/kaart';\n      navigate(next, { replace: true });\n    } catch (err) {\n      setError(err instanceof Error ? err.message : 'Inloggen mislukt.');\n    } finally {\n      setIsSubmitting(false);\n    }\n  };\n\n  return (\n    <div className=\"flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10\">\n      <div className=\"w-full max-w-sm md:max-w-3xl\">\n        <div className={cn('flex flex-col gap-6')}>\n          <img src={logo} alt=\"ARCH.X Digital Twin voor Vaarwegen\" className=\"mx-auto h-12\" />\n          <Card className=\"overflow-hidden py-0 gap-0\">\n            <CardContent className=\"grid p-0 md:grid-cols-2\">\n              <form className=\"p-6 md:p-8\" onSubmit={handleSubmit}>\n                <div className=\"flex flex-col gap-6\">\n                  <div className=\"flex flex-col items-center text-center\">\n                    <h1 className=\"text-2xl font-bold\">Welkom terug</h1>\n                    <p className=\"text-balance text-muted-foreground\">\n                      Log in op het ArchX dashboard\n                    </p>\n                  </div>\n\n                  {error && (\n                    <div className=\"rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n                      {error}\n                    </div>\n                  )}\n\n                  <div className=\"grid gap-2\">\n                    <Label htmlFor=\"email\">E-mailadres</Label>\n                    <Input\n                      id=\"email\"\n                      type=\"email\"\n                      autoComplete=\"email\"\n                      required\n                      value={email}\n                      onChange={(e) => setEmail(e.target.value)}\n                    />\n                  </div>\n\n                  <div className=\"grid gap-2\">\n                    <Label htmlFor=\"password\">Wachtwoord</Label>\n                    <Input\n                      id=\"password\"\n                      type=\"password\"\n                      autoComplete=\"current-password\"\n                      required\n                      value={password}\n                      onChange={(e) => setPassword(e.target.value)}\n                    />\n                  </div>\n\n                  <Button type=\"submit\" className=\"w-full\" disabled={isSubmitting || isLoading}>\n                    {isSubmitting ? 'Bezig...' : 'Inloggen'}\n                  </Button>\n                </div>\n              </form>\n              <div className=\"relative hidden bg-muted md:block\">\n                <img\n                  src={loginBg}\n                  alt=\"Sleephopperzuiger Charlock\"\n                  className=\"absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale\"\n                />\n              </div>\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default Login;\n","import { Navigate, Outlet, useLocation } from 'react-router';\n\nimport { useAuth } from '@/js/auth';\n\nconst ProtectedLayout = () => {\n  const { user, isLoading } = useAuth();\n  const location = useLocation();\n\n  if (isLoading) {\n    return null;\n  }\n\n  if (!user) {\n    const next = location.pathname + location.search + location.hash;\n    return <Navigate to={`/login?next=${encodeURIComponent(next)}`} replace />;\n  }\n\n  return <Outlet />;\n};\n\nexport default ProtectedLayout;\n","import { Outlet } from 'react-router';\n\nimport { AppSidebar } from '@/js/components/app-sidebar';\nimport { SidebarInset, SidebarProvider, SidebarTrigger } from '@/js/components/ui/sidebar';\nimport { Toaster } from '@/js/components/ui/sonner';\n\n// Onder deze vensterbreedte start de zijbalk ingeklapt (alleen pictogrammen), zodat\n// de kaart op een laptop de ruimte krijgt. Daarna bepaalt het knopje in de zijbalk.\nexport const ZIJBALK_OPEN_VANAF_PX = 1440;\n\n// Geen kopbalk: elke pagina zet zelf haar PageHeader (h1) bovenaan; de kaart\n// en de omgevingsmonitor vullen het hele venster.\nconst AppShellLayout = () => (\n  <SidebarProvider defaultOpen={window.innerWidth >= ZIJBALK_OPEN_VANAF_PX}>\n    <AppSidebar />\n    <SidebarInset className=\"min-w-0\">\n      {/* Op een telefoon is de zijbalk een uitschuifpaneel; zonder kopbalk is dit de\n          enige knop die hem opent. Een eigen regel in de opbouw, zodat hij geen\n          paginatitel of kaartpaneel bedekt (de kaartpagina's trekken deze hoogte af). */}\n      <div className=\"flex h-12 shrink-0 items-center border-b px-2 md:hidden\">\n        <SidebarTrigger className=\"size-10\" aria-label=\"Menu openen\" />\n      </div>\n      <div className=\"@container/main flex flex-1 flex-col gap-4 p-4 lg:p-6\">\n        <Outlet />\n      </div>\n    </SidebarInset>\n    <Toaster />\n  </SidebarProvider>\n);\n\nexport default AppShellLayout;\n","import PlaatmigratiePage from './plaatmigratie/PlaatmigratiePage';\n\nexport default function Plaatmigratie() {\n  return <PlaatmigratiePage />;\n}\n","import { Navigate } from 'react-router';\n\nimport { useAuth } from '@/js/auth';\nimport Analyse from '@/js/pages/Analyse';\n\nconst AnalyseRoute = () => {\n  const { user } = useAuth();\n\n  if (!user?.has_analyse_access) {\n    return <Navigate to=\"/app/kaart\" replace />;\n  }\n\n  return <Analyse />;\n};\n\nexport default AnalyseRoute;\n","import { Check, Pencil, X } from 'lucide-react';\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router';\n\nimport type { SurveyDataset } from '@/js/api';\n\nimport {\n  apiDatasetsReprocessCreate,\n  apiDatasetsRetrieve,\n  apiDatasetsSoftDeleteCreate,\n  apiSurveysPartialUpdate,\n} from '@/js/api';\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport { Input } from '@/js/components/ui/input';\nimport { Separator } from '@/js/components/ui/separator';\nimport { Skeleton } from '@/js/components/ui/skeleton';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\nconst DataDatasetDetail = () => {\n  const { id } = useParams();\n  const navigate = useNavigate();\n  const [dataset, setDataset] = useState<SurveyDataset | null>(null);\n  const [loading, setLoading] = useState(true);\n  const [actionLoading, setActionLoading] = useState(false);\n  const [editingDate, setEditingDate] = useState(false);\n  const [dateValue, setDateValue] = useState('');\n\n  const fetchDataset = async () => {\n    setLoading(true);\n    try {\n      const { data } = await apiDatasetsRetrieve({ path: { id: Number(id) } });\n      setDataset(data ?? null);\n    } catch {\n      setDataset(null);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  useEffect(() => {\n    fetchDataset();\n  }, [id]);\n\n  const handleReprocess = async () => {\n    if (!dataset) return;\n    setActionLoading(true);\n    try {\n      await apiDatasetsReprocessCreate({\n        path: { id: dataset.id },\n      });\n      fetchDataset();\n    } finally {\n      setActionLoading(false);\n    }\n  };\n\n  const handleDelete = async () => {\n    if (!dataset) return;\n    setActionLoading(true);\n    try {\n      await apiDatasetsSoftDeleteCreate({ path: { id: dataset.id } });\n      navigate('/app/data/datasets');\n    } finally {\n      setActionLoading(false);\n    }\n  };\n\n  const handleDateEdit = () => {\n    setDateValue(dataset?.survey_date ?? '');\n    setEditingDate(true);\n  };\n\n  const handleDateSave = async () => {\n    if (!dataset) return;\n    setActionLoading(true);\n    try {\n      await apiSurveysPartialUpdate({\n        path: { id: dataset.survey },\n        body: { survey_date: dateValue || null },\n      });\n      setEditingDate(false);\n      fetchDataset();\n    } finally {\n      setActionLoading(false);\n    }\n  };\n\n  if (loading) {\n    return (\n      <div className=\"grid gap-4\">\n        <Skeleton className=\"h-32 rounded-xl\" />\n        <Skeleton className=\"h-48 rounded-xl\" />\n      </div>\n    );\n  }\n\n  if (!dataset) {\n    return (\n      <PageHeader title=\"Dataset niet gevonden\" description=\"De gevraagde dataset bestaat niet.\" />\n    );\n  }\n\n  const surveyDateFormatted = dataset.survey_date\n    ? new Date(dataset.survey_date + 'T00:00:00').toLocaleDateString('nl-NL')\n    : null;\n\n  const metaRows: [string, string | null][] = [\n    ['Baggervak', dataset.baggervak_code],\n    ['Status', dataset.status ?? null],\n    ['Aangemaakt', new Date(dataset.created).toLocaleString('nl-NL')],\n    ['Laatst gewijzigd', new Date(dataset.modified).toLocaleString('nl-NL')],\n  ];\n\n  const rasterRows: [string, string | null][] = [\n    [\n      'Bbox (minx, miny)',\n      dataset.bbox_minx != null\n        ? `${dataset.bbox_minx.toFixed(1)}, ${dataset.bbox_miny?.toFixed(1)}`\n        : null,\n    ],\n    [\n      'Bbox (maxx, maxy)',\n      dataset.bbox_maxx != null\n        ? `${dataset.bbox_maxx.toFixed(1)}, ${dataset.bbox_maxy?.toFixed(1)}`\n        : null,\n    ],\n    [\n      'Resolutie (x, y)',\n      dataset.resolution_x != null ? `${dataset.resolution_x} × ${dataset.resolution_y} m` : null,\n    ],\n    ['Diepte min', dataset.depth_min != null ? `${dataset.depth_min.toFixed(2)} m` : null],\n    ['Diepte max', dataset.depth_max != null ? `${dataset.depth_max.toFixed(2)} m` : null],\n    ['Diepte gemiddeld', dataset.depth_mean != null ? `${dataset.depth_mean.toFixed(2)} m` : null],\n  ];\n\n  return (\n    <div className=\"grid gap-4\">\n      <PageHeader\n        title={`Dataset ${dataset.baggervak_code}`}\n        description=\"Eigenschappen, rastergegevens en beheeracties.\"\n      >\n        <Badge\n          variant={\n            dataset.status === 'ready' || dataset.status === 'published' ? 'outline' : 'secondary'\n          }\n        >\n          {dataset.status}\n        </Badge>\n      </PageHeader>\n\n      <PageSection title=\"Eigenschappen\">\n        <dl className=\"grid gap-2 text-sm sm:grid-cols-2\">\n          <div className=\"flex items-center justify-between gap-2 rounded-lg border px-3 py-2\">\n            <dt className=\"text-muted-foreground\">Peildatum</dt>\n            <dd className=\"flex items-center gap-1 font-medium\">\n              {editingDate ? (\n                <>\n                  <Input\n                    type=\"date\"\n                    value={dateValue}\n                    onChange={(e) => setDateValue(e.target.value)}\n                    className=\"h-7 w-auto\"\n                    onKeyDown={(e) => {\n                      if (e.key === 'Enter') handleDateSave();\n                      if (e.key === 'Escape') setEditingDate(false);\n                    }}\n                  />\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-6\"\n                    onClick={handleDateSave}\n                    disabled={actionLoading}\n                  >\n                    <Check className=\"size-3.5\" />\n                  </Button>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-6\"\n                    onClick={() => setEditingDate(false)}\n                  >\n                    <X className=\"size-3.5\" />\n                  </Button>\n                </>\n              ) : (\n                <>\n                  {surveyDateFormatted ?? '—'}\n                  <Button variant=\"ghost\" size=\"icon\" className=\"size-6\" onClick={handleDateEdit}>\n                    <Pencil className=\"size-3\" />\n                  </Button>\n                </>\n              )}\n            </dd>\n          </div>\n          {metaRows.map(([label, value]) => (\n            <div key={label} className=\"flex justify-between gap-2 rounded-lg border px-3 py-2\">\n              <dt className=\"text-muted-foreground\">{label}</dt>\n              <dd className=\"font-medium\">{value ?? '—'}</dd>\n            </div>\n          ))}\n        </dl>\n      </PageSection>\n\n      <PageSection title=\"Rastergegevens\">\n        {dataset.bbox_minx == null ? (\n          <p className=\"text-sm text-muted-foreground\">\n            Nog niet beschikbaar — dataset wordt verwerkt.\n          </p>\n        ) : (\n          <dl className=\"grid gap-2 text-sm sm:grid-cols-2\">\n            {rasterRows.map(([label, value]) => (\n              <div key={label} className=\"flex justify-between gap-2 rounded-lg border px-3 py-2\">\n                <dt className=\"text-muted-foreground\">{label}</dt>\n                <dd className=\"font-medium tabular-nums\">{value ?? '—'}</dd>\n              </div>\n            ))}\n          </dl>\n        )}\n      </PageSection>\n\n      <PageSection title=\"Opslagpaden\">\n        <dl className=\"grid gap-2 text-sm\">\n          <div className=\"rounded-lg border px-3 py-2\">\n            <dt className=\"text-muted-foreground\">Origineel</dt>\n            <dd className=\"mt-0.5 truncate font-mono text-xs\">{dataset.raw_blob_path || '—'}</dd>\n          </div>\n          <div className=\"rounded-lg border px-3 py-2\">\n            <dt className=\"text-muted-foreground\">COG</dt>\n            <dd className=\"mt-0.5 truncate font-mono text-xs\">{dataset.cog_blob_path || '—'}</dd>\n          </div>\n        </dl>\n      </PageSection>\n\n      <PageSection title=\"Beheeracties\">\n        <Separator className=\"mb-4\" />\n        <div className=\"flex flex-wrap gap-2\">\n          <Button variant=\"outline\" size=\"sm\" disabled={actionLoading} onClick={handleReprocess}>\n            Opnieuw verwerken\n          </Button>\n          <Button variant=\"destructive\" size=\"sm\" disabled={actionLoading} onClick={handleDelete}>\n            Verwijderen\n          </Button>\n        </div>\n      </PageSection>\n    </div>\n  );\n};\n\nexport default DataDatasetDetail;\n","import { Trash2 } from 'lucide-react';\nimport { useEffect, useState } from 'react';\n\nimport type { SurveyDataset } from '@/js/api';\n\nimport { apiDatasetsList, apiDatasetsPurgeTrashCreate } from '@/js/api';\nimport { Badge } from '@/js/components/ui/badge';\nimport { Button } from '@/js/components/ui/button';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/js/components/ui/table';\nimport PageHeader from '@/js/pages/PageHeader';\nimport PageSection from '@/js/pages/PageSection';\n\nconst DataTrash = () => {\n  const [datasets, setDatasets] = useState<SurveyDataset[]>([]);\n  const [loading, setLoading] = useState(true);\n  const [purging, setPurging] = useState(false);\n\n  const fetchDeleted = async () => {\n    setLoading(true);\n    try {\n      const { data } = await apiDatasetsList({ query: { status: 'deleted', limit: 1000 } });\n      setDatasets(data?.results ?? []);\n    } catch {\n      setDatasets([]);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const handlePurge = async () => {\n    if (!window.confirm('Weet je zeker dat je alle verwijderde datasets permanent wilt verwijderen? Dit kan niet ongedaan worden gemaakt.')) {\n      return;\n    }\n    setPurging(true);\n    try {\n      await apiDatasetsPurgeTrashCreate();\n      await fetchDeleted();\n    } catch {\n      // ignore\n    } finally {\n      setPurging(false);\n    }\n  };\n\n  useEffect(() => {\n    fetchDeleted();\n  }, []);\n\n  return (\n    <div className=\"grid gap-4\">\n      <PageHeader\n        title=\"Prullenbak\"\n        description=\"Verwijderde datasets en hun bestanden in de opslag. Gebruik 'Prullenbak legen' om alles permanent te verwijderen.\"\n      />\n\n      <PageSection title=\"Verwijderde datasets\">\n        {loading ? (\n          <p className=\"text-sm text-muted-foreground\">Laden…</p>\n        ) : datasets.length === 0 ? (\n          <p className=\"text-sm text-muted-foreground\">Prullenbak is leeg.</p>\n        ) : (\n          <>\n            <div className=\"mb-4 flex items-center justify-between\">\n              <p className=\"text-sm text-muted-foreground\">\n                {datasets.length} dataset(s) in prullenbak\n              </p>\n              <Button\n                variant=\"destructive\"\n                size=\"sm\"\n                disabled={purging}\n                onClick={handlePurge}\n              >\n                <Trash2 className=\"mr-2 h-4 w-4\" />\n                {purging ? 'Bezig met verwijderen…' : 'Prullenbak legen'}\n              </Button>\n            </div>\n            <Table>\n              <TableHeader>\n                <TableRow>\n                  <TableHead>Baggervak</TableHead>\n                  <TableHead>Status</TableHead>\n                  <TableHead>Verwijderd op</TableHead>\n                </TableRow>\n              </TableHeader>\n              <TableBody>\n                {datasets.map((ds) => (\n                  <TableRow key={ds.id}>\n                    <TableCell className=\"font-medium\">{ds.baggervak_code}</TableCell>\n                    <TableCell>\n                      <Badge variant=\"destructive\">Verwijderd</Badge>\n                    </TableCell>\n                    <TableCell className=\"text-muted-foreground\">\n                      {new Date(ds.modified).toLocaleString('nl-NL')}\n                    </TableCell>\n                  </TableRow>\n                ))}\n              </TableBody>\n            </Table>\n          </>\n        )}\n      </PageSection>\n    </div>\n  );\n};\n\nexport default DataTrash;\n","import * as Sentry from '@sentry/react';\nimport { parse as cookieParse } from 'cookie';\nimport { RouterProvider } from 'react-router/dom';\n\nimport { client } from '@/js/api/client.gen';\nimport { AuthProvider } from '@/js/auth';\nimport router from '@/js/routes';\n\nclient.instance.interceptors.request.use((request) => {\n  const { csrftoken } = cookieParse(document.cookie);\n  if (request.headers && csrftoken) {\n    request.headers['X-CSRFTOKEN'] = csrftoken;\n  }\n  return request;\n});\n\nconst App = () => (\n  <Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>\n    <AuthProvider>\n      <RouterProvider router={router} />\n    </AuthProvider>\n  </Sentry.ErrorBoundary>\n);\n\nexport default App;\n","// import pages\nimport * as Sentry from '@sentry/browser';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport '@fontsource/ibm-plex-sans/400.css';\nimport '@fontsource/ibm-plex-sans/500.css';\nimport '@fontsource/ibm-plex-sans/600.css';\nimport '@fontsource/ibm-plex-mono/400.css';\nimport '@fontsource/ibm-plex-mono/500.css';\nimport '@/css/style.css';\n\nSentry.init({\n  dsn: window.SENTRY_DSN,\n  release: window.COMMIT_SHA,\n});\n\nconst root = createRoot(document.getElementById('react-app') as HTMLElement);\nroot.render(<App />);\n","import { Switch as SwitchPrimitive } from \"radix-ui\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/js/lib/utils\"\n\nfunction Switch({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root> & {\n  size?: \"sm\" | \"default\"\n}) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-size={size}\n      className={cn(\n        \"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80\",\n        className\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          \"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground\"\n        )}\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch }\n","/** AXT1: 8-byte header, JSON tile index, then lossless PNGs.\n * One blob-scoped SAS protects the package; each tile is a direct HTTP range. */\ntype Index = {\n  minzoom: number;\n  maxzoom: number;\n  bounds: [number, number, number, number];\n  tiles: Record<string, [number, number]>;\n  dataStart: number;\n};\nconst indices = new Map<string, Promise<Index>>();\n\nasync function range(url: string, start: number, length: number, signal?: AbortSignal) {\n  const response = await fetch(url, {\n    headers: { Range: `bytes=${start}-${start + length - 1}` },\n    signal,\n  });\n  if (response.status !== 206) throw new Error(`Tile package range failed (${response.status})`);\n  const bytes = await response.arrayBuffer();\n  if (bytes.byteLength !== length) throw new Error('Incomplete tile package range');\n  return bytes;\n}\n\nasync function index(url: string): Promise<Index> {\n  let pending = indices.get(url);\n  if (pending) {\n    indices.delete(url);\n    indices.set(url, pending);\n    return pending;\n  }\n  pending = (async () => {\n    const header = new DataView(await range(url, 0, 8, AbortSignal.timeout(30_000)));\n    if (header.getUint32(0) !== 0x41585431) throw new Error('Unknown tile package format');\n    const length = header.getUint32(4, true);\n    if (length < 2 || length > 16 * 1024 * 1024) throw new Error('Invalid tile package index');\n    const value = JSON.parse(\n      new TextDecoder().decode(await range(url, 8, length, AbortSignal.timeout(30_000)))\n    );\n    if (\n      !Array.isArray(value.bounds) ||\n      value.bounds.length !== 4 ||\n      !value.bounds.every(Number.isFinite) ||\n      !Number.isInteger(value.minzoom) ||\n      !Number.isInteger(value.maxzoom) ||\n      value.minzoom < 0 ||\n      value.maxzoom > 22 ||\n      value.minzoom > value.maxzoom ||\n      !value.tiles ||\n      typeof value.tiles !== 'object'\n    )\n      throw new Error('Invalid tile package metadata');\n    return { ...value, dataStart: 8 + length };\n  })();\n  indices.set(url, pending);\n  while (indices.size > 64) indices.delete(indices.keys().next().value!);\n  pending.catch(() => {\n    if (indices.get(url) === pending) indices.delete(url);\n  });\n  return pending;\n}\n\nexport async function displayTilesProtocol(\n  request: { url: string; type?: string },\n  controller: AbortController\n): Promise<{ data: any }> {\n  const [url, tile] = request.url.slice('pngtiles://'.length).split('#');\n  const parsed = new URL(url);\n  if (!['https:', 'http:'].includes(parsed.protocol)) throw new Error('Invalid tile package URL');\n  const metadata = await index(url);\n  controller.signal.throwIfAborted();\n  if (request.type === 'json')\n    return {\n      data: {\n        tilejson: '3.0.0',\n        minzoom: metadata.minzoom,\n        maxzoom: metadata.maxzoom,\n        bounds: metadata.bounds,\n        tiles: [`pngtiles://${url}#{z}/{x}/{y}`],\n      },\n    };\n  if (request.type !== 'image' || !/^\\d+\\/\\d+\\/\\d+$/.test(tile || ''))\n    throw new Error('Invalid tile request');\n  const entry = metadata.tiles[tile];\n  if (!entry) {\n    // MapLibre may request one border tile due to its bounds rounding. Only\n    // proven outside tiles can be empty; a hole inside coverage is an error.\n    const [z, x, y] = tile.split('/').map(Number);\n    const n = 2 ** z;\n    const west = (x / n) * 360 - 180,\n      east = ((x + 1) / n) * 360 - 180;\n    const lat = (row: number) =>\n      (Math.atan(Math.sinh(Math.PI * (1 - (2 * row) / n))) * 180) / Math.PI;\n    const [w, s, e, north] = metadata.bounds;\n    if (east < w || west > e || lat(y) < s || lat(y + 1) > north)\n      return { data: await createImageBitmap(new ImageData(256, 256)) };\n    throw new Error('Missing tile inside prepared coverage');\n  }\n  const [offset, length] = entry;\n  if (\n    !Number.isSafeInteger(offset) ||\n    offset < 0 ||\n    !Number.isSafeInteger(length) ||\n    length < 1 ||\n    length > 1024 * 1024\n  )\n    throw new Error('Invalid tile package offset');\n  const bytes = await range(url, metadata.dataStart + offset, length, controller.signal);\n  const bitmap = await createImageBitmap(new Blob([bytes], { type: 'image/png' }));\n  if (controller.signal.aborted) {\n    bitmap.close();\n    controller.signal.throwIfAborted();\n  }\n  return { data: bitmap };\n}\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Toggle as TogglePrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '@/js/lib/utils';\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        outline:\n          'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',\n      },\n      size: {\n        default: 'h-9 px-2 min-w-9',\n        sm: 'h-8 px-1.5 min-w-8',\n        lg: 'h-10 px-2.5 min-w-10',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Toggle({\n  className,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive.Root\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Toggle, toggleVariants };\n","'use client';\n\nimport { type VariantProps } from 'class-variance-authority';\nimport { ToggleGroup as ToggleGroupPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { toggleVariants } from '@/js/components/ui/toggle';\nimport { cn } from '@/js/lib/utils';\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n  }\n>({\n  size: 'default',\n  variant: 'default',\n  spacing: 0,\n});\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n  }) {\n  return (\n    <ToggleGroupPrimitive.Root\n      data-slot=\"toggle-group\"\n      data-variant={variant}\n      data-size={size}\n      data-spacing={spacing}\n      style={{ '--gap': spacing } as React.CSSProperties}\n      className={cn(\n        'group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs',\n        className\n      )}\n      {...props}\n    >\n      <ToggleGroupContext.Provider value={{ variant, size, spacing }}>\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive.Root>\n  );\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant,\n  size,\n  ...props\n}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>) {\n  const context = React.useContext(ToggleGroupContext);\n\n  return (\n    <ToggleGroupPrimitive.Item\n      data-slot=\"toggle-group-item\"\n      data-variant={context.variant || variant}\n      data-size={context.size || size}\n      data-spacing={context.spacing}\n      className={cn(\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size,\n        }),\n        'w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10',\n        'data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l',\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  );\n}\n\nexport { ToggleGroup, ToggleGroupItem };\n","/**\n * Dieptekleuren voor een peil (NAP, LAT) — eigen chunk `palet-diepte.cjs` in de\n * tegelrenderer.\n *\n * Staat los van de verschilkleuren, zodat de renderer per palet een eigen versie\n * heeft (`renderer_version(palet)` in `backend/surveys/display_tiles.py`): een\n * nieuwe dieptekleur maakt alleen de NAP/LAT-pakketten en de omgeving ongeldig,\n * niet de ~7,7 uur aan NGD/IVD/OHD-pakketten. Importeer via `depth-colors`.\n */\n\nexport interface AbsoluteColorConfig {\n  mode: 'absolute';\n  /** Deepest depth in the ramp, in the chosen datum (typically negative) */\n  min: number;\n  /** Shallowest depth in the ramp, in the chosen datum */\n  max: number;\n  /**\n   * Het gekozen peil in m NAP. Nul voor NAP zelf; voor LAT het\n   * herleidingsvlak van het baggervak (rond -1,7 m). De COG levert altijd\n   * NAP-waarden, dus dit wordt eraf getrokken voordat de kleur bepaald wordt.\n   */\n  datum?: number;\n}\n\n/** Default depth range for the Eemsgeul navigation channel */\nexport const DIEPTE_STANDAARD: AbsoluteColorConfig = { mode: 'absolute', min: -16, max: -10 };\n\n/** Color stops: [t, R, G, B] where t is 0..1 (deep→shallow) */\nconst STOPS: [number, number, number, number][] = [\n  [0.0, 8, 30, 120],\n  [0.15, 10, 60, 160],\n  [0.3, 20, 120, 200],\n  [0.45, 40, 180, 170],\n  [0.6, 60, 200, 120],\n  [0.75, 140, 210, 60],\n  [0.9, 220, 230, 40],\n  [1.0, 255, 245, 20],\n];\n\nfunction interpolateColor(t: number): [number, number, number] {\n  const clamped = Math.max(0, Math.min(1, t));\n  for (let i = 0; i < STOPS.length - 1; i++) {\n    const [t0, r0, g0, b0] = STOPS[i];\n    const [t1, r1, g1, b1] = STOPS[i + 1];\n    if (clamped >= t0 && clamped <= t1) {\n      const s = (clamped - t0) / (t1 - t0);\n      return [\n        Math.round(r0 + s * (r1 - r0)),\n        Math.round(g0 + s * (g1 - g0)),\n        Math.round(b0 + s * (b1 - b0)),\n      ];\n    }\n  }\n  const last = STOPS[STOPS.length - 1];\n  return [last[1], last[2], last[3]];\n}\n\n/**\n * Ondieper dan `max` (−10 m): de plaat. Verloop geel → oranje-zand → bruin,\n * [meter boven max, R, G, B]. Getoetst op ≥ 3 ΔE2000 per meter (palet-diepte.spec);\n * nagerekend met skimage: minimaal 3,43 tussen −10 en −9 m.\n */\nconst ZAND_STOPS: [number, number, number, number][] = [\n  [0, 255, 245, 20],\n  [3, 245, 205, 80],\n  [6, 225, 160, 80],\n  [9, 180, 115, 55],\n  [12, 120, 75, 35],\n];\nexport const ZAND_BEREIK_M = 12;\n\nfunction zandKleur(boven: number): [number, number, number] {\n  const m = Math.max(0, Math.min(ZAND_BEREIK_M, boven));\n  for (let i = 0; i < ZAND_STOPS.length - 1; i++) {\n    const [m0, r0, g0, b0] = ZAND_STOPS[i];\n    const [m1, r1, g1, b1] = ZAND_STOPS[i + 1];\n    if (m >= m0 && m <= m1) {\n      const s = (m - m0) / (m1 - m0);\n      return [\n        Math.round(r0 + s * (r1 - r0)),\n        Math.round(g0 + s * (g1 - g0)),\n        Math.round(b0 + s * (b1 - b0)),\n      ];\n    }\n  }\n  const last = ZAND_STOPS[ZAND_STOPS.length - 1];\n  return [last[1], last[2], last[3]];\n}\n\nexport function depthToColor(\n  depth: number,\n  config: AbsoluteColorConfig = DIEPTE_STANDAARD\n): [number, number, number] {\n  const inDatum = depth - (config.datum ?? 0);\n  if (inDatum <= config.max) {\n    return interpolateColor((inDatum - config.min) / (config.max - config.min));\n  }\n  return zandKleur(inDatum - config.max);\n}\n\n/** Color stops for the absolute-mode legend: van `min` tot `max` + 12 m (+2 m NAP). */\nexport function getLegendStops(\n  config: AbsoluteColorConfig = DIEPTE_STANDAARD,\n  count = 10\n): { depth: number; color: string }[] {\n  const bovenkant = config.max + ZAND_BEREIK_M;\n  const stops: { depth: number; color: string }[] = [];\n  for (let i = 0; i < count; i++) {\n    const depth = config.min + (i / (count - 1)) * (bovenkant - config.min);\n    const [r, g, b] = depthToColor(depth, { ...config, datum: 0 });\n    stops.push({ depth, color: `rgb(${r},${g},${b})` });\n  }\n  return stops;\n}\n","/**\n * Verschil- en kanskleuren (NGD, IVD, OHD) — eigen chunk `palet-verschil.cjs`\n * in de tegelrenderer. Zie `palet-diepte.ts` voor waarom de paletten los staan.\n * Importeer via `depth-colors`.\n */\nimport type { Colormap } from './depth-colors';\n\n// ---------------------------------------------------------------------------\n// Difference mode: discrete RWS \"afstand tot referentievlak\" colormap\n// ---------------------------------------------------------------------------\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\nexport interface DifferenceBin {\n  /** Lower bound (inclusive) of the difference range in meters */\n  min: number;\n  /** Upper bound (exclusive) of the difference range in meters */\n  max: number;\n  color: string;\n  rgb: [number, number, number];\n  label: string;\n  /**\n   * Waar deze klasse als kleurstop ligt in de continue schaal, in meters.\n   *\n   * Het midden van de klasse. De twee open klassen aan de uiteinden krijgen\n   * één klassebreedte voorbij hun grens, zodat het verloop daar doorloopt in\n   * plaats van meteen vast te lopen.\n   */\n  midden: number;\n}\n\n/**\n * De klasse-indeling van de aanlevering: positief is boven het referentievlak\n * (te ondiep), negatief eronder.\n *\n * De grenzen zijn niet lineair, en dat is het ontwerp: rond nul stapjes van\n * 0,1 m, ver van nul stappen van 1 en 2 m. Precies waar het besluit valt — ligt\n * de bodem boven of onder het vlak — is de indeling het fijnst.\n *\n * Herkomst van de kleuren: de legenda van `IJssel-Baggervak-feb-2020.pdf`\n * (Van den Herik, blad 1, \"Diff JMP-feb-2020 Feb met OLR-250 IJssel\"),\n * uitgelezen uit de vectorlaag. Ze waren eerder met de hand benaderd; deze\n * waarden staan vast in `depth-colors.spec.ts`, dus benader ze niet opnieuw.\n *\n * Eén bewuste afwijking: de plot geeft `-0,2 tot -0,1` en `-0,5 tot -0,2`\n * allebei #38A800, waardoor die twee klassen niet uit elkaar te houden zijn.\n * Dat is precies het bereik waar de baggerbeslissing valt, dus de tweede krijgt\n * hier #267300 — de volgende stap in dezelfde ESRI-reeks. Openstaande vraag aan\n * Van den Herik of hun dubbeling opzet is (#114).\n */\nexport const DIFFERENCE_BINS: DifferenceBin[] = [\n  { min: 2, max: Infinity, color: '#730000', label: '> +2 m', midden: 2.5 },\n  { min: 1, max: 2, color: '#F73B23', label: '+1 tot +2 m', midden: 1.5 },\n  { min: 0.5, max: 1, color: '#E69800', label: '+0,5 tot +1 m', midden: 0.75 },\n  { min: 0.2, max: 0.5, color: '#FFD37F', label: '+0,2 tot +0,5 m', midden: 0.35 },\n  { min: 0.1, max: 0.2, color: '#FFFF00', label: '+0,1 tot +0,2 m', midden: 0.15 },\n  { min: 0, max: 0.1, color: '#FFFFBE', label: '0 tot +0,1 m', midden: 0.05 },\n  { min: -0.1, max: 0, color: '#A3FF73', label: '-0,1 tot 0 m', midden: -0.05 },\n  { min: -0.2, max: -0.1, color: '#38A800', label: '-0,2 tot -0,1 m', midden: -0.15 },\n  { min: -0.5, max: -0.2, color: '#267300', label: '-0,5 tot -0,2 m', midden: -0.35 },\n  { min: -1, max: -0.5, color: '#00C5FF', label: '-1 tot -0,5 m', midden: -0.75 },\n  { min: -2, max: -1, color: '#0070FF', label: '-2 tot -1 m', midden: -1.5 },\n  { min: -Infinity, max: -2, color: '#004DA8', label: '< -2 m', midden: -2.5 },\n].map((bin) => ({ ...bin, rgb: hexToRgb(bin.color) }));\n\n/** Match diff (m) to the first bin where diff >= bin.min. Bins are ordered high→low. */\nfunction differenceToBinColor(diff: number): [number, number, number] {\n  for (const bin of DIFFERENCE_BINS) {\n    if (diff >= bin.min) return bin.rgb;\n  }\n  return DIFFERENCE_BINS[DIFFERENCE_BINS.length - 1].rgb;\n}\n\n/**\n * Dezelfde kleuren als verloop, met de klassen als stops op hun midden.\n *\n * Afgeleid uit dezelfde tabel en niet apart gekozen: zo blijft er één palet in\n * deze module, en wisselen tussen de twee schalen verandert de indeling van het\n * beeld zonder de kleurtaal te verruilen.\n */\nfunction differenceToRampColor(diff: number): [number, number, number] {\n  // De tabel loopt van ondiep naar diep; voor interpoleren is oplopend handiger.\n  const stops = DIFFERENCE_BINS.slice().reverse();\n\n  if (diff <= stops[0].midden) return stops[0].rgb;\n  for (let i = 0; i < stops.length - 1; i++) {\n    const laag = stops[i];\n    const hoog = stops[i + 1];\n    if (diff <= hoog.midden) {\n      const s = (diff - laag.midden) / (hoog.midden - laag.midden);\n      return [\n        Math.round(laag.rgb[0] + s * (hoog.rgb[0] - laag.rgb[0])),\n        Math.round(laag.rgb[1] + s * (hoog.rgb[1] - laag.rgb[1])),\n        Math.round(laag.rgb[2] + s * (hoog.rgb[2] - laag.rgb[2])),\n      ];\n    }\n  }\n  return stops[stops.length - 1].rgb;\n}\n\nexport function differenceToColor(\n  diff: number,\n  colormap: Colormap = 'discreet'\n): [number, number, number] {\n  // `kans` heeft ook de spreiding nodig en loopt via {@link kansToColor}; wie\n  // hier toch belandt, krijgt de klassen in plaats van een verzonnen kans.\n  return colormap === 'continu' ? differenceToRampColor(diff) : differenceToBinColor(diff);\n}\n\n// ---------------------------------------------------------------------------\n// Kans mode: kans boven het referentievlak bij de volgende peiling (WP6)\n// ---------------------------------------------------------------------------\n\n/** Standaardnormale verdelingsfunctie; Abramowitz & Stegun 7.1.26, fout < 1,5·10⁻⁷. */\nfunction normaleVerdeling(x: number): number {\n  const z = Math.abs(x) / Math.SQRT2;\n  const t = 1 / (1 + 0.3275911 * z);\n  const erf =\n    1 -\n    ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) *\n      t *\n      Math.exp(-z * z);\n  return x >= 0 ? 0.5 * (1 + erf) : 0.5 * (1 - erf);\n}\n\n/**\n * Kans dat de bodem bij de volgende peiling boven het referentievlak ligt.\n *\n * De getoonde peiling is de verwachting; σ is hoeveel de bodem in dit vak in\n * een maand verandert. Dat is het enige model dat in de spike van 29 september\n * stabiel beter bleek dan de historische frequentie, en alleen op één maand.\n *\n * @param diff diepte min referentie in meter; positief is boven het vlak.\n * @param sigma maandspreiding van het vak in meter.\n */\nexport function kansBoven(diff: number, sigma: number): number {\n  return normaleVerdeling(diff / sigma);\n}\n\nexport interface KansBin {\n  /** Ondergrens (inclusief) van de kans. */\n  min: number;\n  color: string;\n  rgb: [number, number, number];\n  label: string;\n}\n\n/**\n * Vijf klassen, zonder oordeel: wanneer een vak \"rood\" wordt, is een keuze van\n * RWS en niet van de kaart. De laagste klasse is bewust geen wit of\n * transparant: \"vrijwel zeker niet\" is een uitspraak, geen gat in de data.\n */\nexport const KANS_BINS: KansBin[] = [\n  { min: 0.8, color: '#7F0000', label: '> 80 %' },\n  { min: 0.5, color: '#D7301F', label: '50–80 %' },\n  { min: 0.2, color: '#FC8D59', label: '20–50 %' },\n  { min: 0.05, color: '#FDD49E', label: '5–20 %' },\n  { min: 0, color: '#D9E6F2', label: '< 5 %' },\n].map((bin) => ({ ...bin, rgb: hexToRgb(bin.color) }));\n\nexport function kansToColor(kans: number): [number, number, number] {\n  for (const bin of KANS_BINS) {\n    if (kans >= bin.min) return bin.rgb;\n  }\n  return KANS_BINS[KANS_BINS.length - 1].rgb;\n}\n","/**\n * Depth→color mapping for bathymetric COGs.\n *\n * Two modes:\n * - Absolute (NAP, LAT): continuous blue→yellow ramp for raw depth values.\n * - Difference (NGD/IVD/OHD): discrete RWS colormap for distance to reference level.\n *\n * NAP en LAT zijn allebei een *peil*: een vlak waar diepte vanaf gemeten wordt.\n * NGD, IVD en OHD zijn *ontwerpdieptes*: een grens waar de bodem onder moet\n * blijven. Dat is het onderscheid tussen de twee modes. Een LAT-kaart in\n * verschilkleuren zou onzin zijn — de bodem ligt overal veertien meter onder\n * LAT, dus alles zou in dezelfde bak vallen. Zie #114.\n *\n * De kleuren zelf staan in `palet-diepte.ts` en `palet-verschil.ts`: elk palet is\n * een eigen chunk in de tegelrenderer, met een eigen rendererversie.\n */\n\nimport { DIEPTE_STANDAARD, depthToColor, type AbsoluteColorConfig } from './palet-diepte';\nimport { differenceToColor } from './palet-verschil';\n\nexport { DIEPTE_STANDAARD, depthToColor, getLegendStops } from './palet-diepte';\nexport type { AbsoluteColorConfig } from './palet-diepte';\nexport {\n  DIFFERENCE_BINS,\n  KANS_BINS,\n  differenceToColor,\n  kansBoven,\n  kansToColor,\n} from './palet-verschil';\nexport type { DifferenceBin, KansBin } from './palet-verschil';\n\n/**\n * Waartegen de kaart kleurt.\n *\n * `verschil` is de verschilkaart t.o.v. de vorige peiling van hetzelfde vak\n * (bestek 2026, art. 32.3.2 lid 2). Die rekent de browser uit twee peilingen\n * (`bathycog-delta.ts`, kleuren in `palet-verandering.ts`); deze module en de\n * tegelrenderer kennen hem niet, dus valt hij hier in geen enkele tak.\n *\n * `ivd` staat er alleen nog voor de tegelrenderer, die deze module meebundelt:\n * de kaart biedt hem niet meer aan (IVD komt in het bestek 2026 niet voor), zie\n * {@link KaartReferentie}. De functies hieronder blijven ongewijzigd, omdat elke\n * wijziging erin de rendererversie verschuift en dan alle voorbereide\n * tegelpakketten opnieuw gebouwd moeten worden.\n */\nexport type ReferenceLevel = 'nap' | 'lat' | 'ngd' | 'ivd' | 'ohd' | 'verschil';\n\n/** Wat de kaart kan tonen: alles behalve IVD. */\nexport type KaartReferentie = Exclude<ReferenceLevel, 'ivd'>;\n\n/**\n * Hoe de afstand tot een ontwerpdiepte in kleur wordt gezet.\n *\n * `discreet` is de klasse-indeling van de aanlevering en de standaard: harde\n * grenzen, en daardoor meteen te zien in welke bak een plek valt. `continu`\n * legt dezelfde kleuren als verloop neer, voor wie juist de vorm van de bodem\n * wil zien in plaats van de indeling.\n *\n * `kans` kijkt één peiling vooruit: de kans dat de bodem bij de volgende\n * peiling boven het vlak ligt, met de getoonde peiling als verwachting en de\n * maandspreiding van het vak als onzekerheid (WP6). Zie {@link kansBoven}.\n *\n * Geldt alleen voor een ontwerpdiepte; zie {@link heeftColormapKeuze}.\n */\nexport type Colormap = 'continu' | 'discreet' | 'kans';\n\n/**\n * Of het referentieniveau een keuze tussen de twee schalen kent.\n *\n * Een peil niet. De verschilklassen op NAP loslaten levert één egale kleur op:\n * de bodem ligt overal veertien meter onder NAP, dus alles valt in `< -2 m`.\n * Een peil houdt daarom altijd de doorlopende dieptekleuren.\n */\nexport function heeftColormapKeuze(referentie: ReferenceLevel): boolean {\n  return referentie === 'ngd' || referentie === 'ivd' || referentie === 'ohd';\n}\n\nexport interface DifferenceColorConfig {\n  mode: 'difference';\n  /** Reference depth in m NAP (the NGD/IVD/OHD value for the selected baggervak) */\n  referenceDepth: number;\n  /** Klassen of verloop; zie {@link Colormap}. */\n  colormap: Colormap;\n}\n\nexport type DepthColorConfig = AbsoluteColorConfig | DifferenceColorConfig;\n\n/**\n * Create a COG color function for maplibre-cog-protocol's setColorFunction().\n */\nexport function makeColorFunction(config: DepthColorConfig = DIEPTE_STANDAARD) {\n  return (pixel: any, color: Uint8ClampedArray, metadata: any) => {\n    const raw = pixel[0];\n\n    // Treat NaN, Infinity, null/undefined as transparent (noData).\n    // geotiff.js fills noData=NaN pixels with Infinity via fillValue.\n    if (!Number.isFinite(raw)) {\n      color[3] = 0;\n      return;\n    }\n    if (metadata.noData !== undefined && raw === metadata.noData) {\n      color[3] = 0;\n      return;\n    }\n\n    const depth: number = raw * metadata.scale + metadata.offset;\n\n    let r: number, g: number, b: number;\n    if (config.mode === 'difference' && config.colormap === 'kans') {\n      // Dit pad kent geen spreiding en geen referentieraster; een kans tegen\n      // één plat vlak zou zich voordoen als de echte. Liever niets tekenen.\n      color[3] = 0;\n      return;\n    }\n    if (config.mode === 'difference') {\n      [r, g, b] = differenceToColor(depth - config.referenceDepth, config.colormap);\n    } else {\n      [r, g, b] = depthToColor(depth, config);\n    }\n    color[0] = r;\n    color[1] = g;\n    color[2] = b;\n    color[3] = 255;\n  };\n}\n\n/**\n * Build a DepthColorConfig from baggervak reference levels.\n */\nexport function configForReferenceLevel(\n  refLevel: ReferenceLevel,\n  ngd?: number | null,\n  ivd?: number | null,\n  ohd?: number | null,\n  lat?: number | null,\n  colormap: Colormap = 'discreet'\n): DepthColorConfig {\n  if (refLevel === 'nap') return DIEPTE_STANDAARD;\n\n  // LAT is een peil, geen ontwerpdiepte: dezelfde doorlopende schaal als NAP,\n  // alleen gemeten vanaf een vlak dat rond 1,7 meter lager ligt. Zonder\n  // herleidingsvlak voor dit vak valt de kaart terug op NAP; de legenda zegt\n  // dan ook NAP, dus er ontstaat geen stil verkeerd getal.\n  if (refLevel === 'lat') {\n    return lat == null ? DIEPTE_STANDAARD : { ...DIEPTE_STANDAARD, datum: lat };\n  }\n\n  let target: number | null | undefined;\n  switch (refLevel) {\n    case 'ngd':\n      target = ngd;\n      break;\n    case 'ivd':\n      target = ivd;\n      break;\n    case 'ohd':\n      target = ohd;\n      break;\n  }\n\n  if (target == null) return DIEPTE_STANDAARD;\n\n  return { mode: 'difference', referenceDepth: target, colormap };\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = (chunkId) => {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \"-\" + {\"4\":\"e0541b52f3142396610a\",\"115\":\"fb64724a0bbdf865733c\",\"165\":\"cda2989b5ea4ba30c163\",\"204\":\"3da026481c5e09a78cb1\",\"222\":\"b7c0247a7d9be13699d5\",\"278\":\"10af996ba3d07bd249d8\",\"310\":\"3683a44b33595e8792b4\",\"453\":\"75dae4959005fe0a99cb\",\"493\":\"dc9adca18c0a27875385\",\"506\":\"0d4ff893b3621e538eb5\"}[chunkId] + \".js\";\n};","// This function allow to reference async chunks\n__webpack_require__.miniCssF = (chunkId) => {\n\t// return url for filenames based on template\n\treturn undefined;\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/^blob:/, \"\").replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t792: 0\n};\n\n__webpack_require__.f.j = (chunkId, promises) => {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = (event) => {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkarchx_eemsgeul_frontend\"] = self[\"webpackChunkarchx_eemsgeul_frontend\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [929], () => (__webpack_require__(2743)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","leafPrototypes","getProto","inProgress","dataWebpackPrefix","OMGEVING_TTL_MS","GEEN","useOmgevingKaarten","enabled","kaarten","setKaarten","useState","useEffect","weg","laad","async","r","fetch","ok","json","haalOmgevingKaarten","then","k","timer","setInterval","clearInterval","buttonVariants","cva","variants","variant","default","destructive","outline","secondary","ghost","link","size","xs","sm","lg","icon","defaultVariants","Button","className","asChild","props","Comp","Slot","data-slot","data-variant","data-size","cn","getal","waarde","decimalen","plusteken","tekst","Math","abs","toLocaleString","minimumFractionDigits","maximumFractionDigits","test","NIEUW_LABEL","foto","loding","MAANDEN","variantVoor","referentie","omgevingBronId","kaart","soort","datum","omgevingVoorDatum","isVoorspelling","klaar","filter","tegels","v","find","slice","beste","VARIANTEN","opDatum","a","b","localeCompare","teMonteren","doel","zichtbaarId","vooruit","sort","uit","voeg","id","url","some","m","push","doelId","i","findIndex","j","vooruitIds","Set","has","volgendeVooruit","gemonteerd","aantal","afstand","length","volgendeZichtbaar","huidig","geladen","maandTekst","iso","split","Number","herkomstTekst","bron","verwacht","delen","jaar","trim","d","datumTekst","bronTekst","join","legendaTekst","nieuweBronnenPerMaand","soorten","bronnen","nieuw","add","nieuwsteBodem","nap","API","EEMSGEBIED","north","south","east","west","zoom","SESSIE","light","mapType","styles","stylers","saturation","lightness","standaard","satellite","sessies","Map","ondergrondStijl","ondergrond","version","sources","basemap","type","tiles","tileUrl","tileSize","attribution","layers","source","useGoogleOndergrond","key","useMemo","el","doc","document","getElementById","textContent","JSON","parse","leesGoogleMapTilesKey","setOndergrond","cancelled","sessie","get","encodeURIComponent","sessieAntwoord","method","headers","body","stringify","language","region","Error","status","session","s","viewport","copyright","maakSessie","set","haalGoogleOndergrond","o","err","console","error","Object","entries","$body_","$headers_","$path_","$query_","serializeArrayParam","allowReserved","explode","name","style","value","joinedValues","map","separatorArrayNoExplode","separator","separatorArrayExplode","serializePrimitiveParam","serializeObjectParam","valueOnly","Date","toISOString","values","forEach","separatorObjectExplode","PATH_PARAM_RE","getValidRequestBody","options","hasBody","undefined","bodySerializer","serializedBody","getAuthToken","auth","callback","token","scheme","btoa","createQuerySerializer","array","object","queryParams","search","Array","isArray","serializedArray","serializedObject","serializedPrimitive","checkForExistence","query","includes","buildUrl","instanceBaseUrl","axios","defaults","baseURL","baseUrl","path","querySerializer","_url","startsWith","matches","match","substring","endsWith","replace","replaceValue","defaultPathSerializer","getUrl","paramsSerializer","mergeConfigs","config","mergeHeaders","axiosHeadersKeywords","mergedHeaders","header","iterator","createConfig","override","client","instance","_config","configWithoutAuth","create","getConfig","beforeRequest","opts","security","in","setAuthParams","requestValidator","request","_axios","optsWithoutAuth","response","data","params","responseType","responseValidator","responseTransformer","e","throwOnError","makeMethodFn","makeSseFn","lastEventId","onRequest","onSseError","onSseEvent","sseDefaultRetryDelay","sseMaxRetryAttempts","sseMaxRetryDelay","sseSleepFn","sleep","ms","Promise","resolve","setTimeout","stream","retryDelay","attempt","signal","AbortController","aborted","Headers","requestInit","redirect","Request","_fetch","globalThis","statusText","reader","pipeThrough","TextDecoderStream","getReader","buffer","abortHandler","cancel","addEventListener","done","read","chunks","pop","chunk","lines","dataLines","eventName","line","parsed","parseInt","isNaN","parsedJson","rawData","event","retry","removeEventListener","releaseLock","backoff","min","createStream","createSseClient","connect","delete","head","patch","post","put","setConfig","sse","trace","createClient","authClient","withCredentials","getSession","res","user","interceptors","use","csrftoken","cookieParse","cookie","AuthContext","createContext","AuthProvider","children","setUser","isLoading","setIsLoading","authApi","u","login","useCallback","email","password","isAxiosError","sessionUser","errors","message","logout","Provider","useAuth","ctx","useContext","CATEGORY_COLOR","VakHeatmap","polygon","cells","cols","rows","angleDeg","widthM","heightM","svg","width","height","viewBox","role","aria-label","rings","geom","coordinates","flat","allPoints","p","ys","cx","max","cy","angleRad","PI","ringsLocal","ring","x","y","dx","dy","cos","sin","toObbLocal","innerWidth","innerHeight","scaleX","scaleY","scale","toSvg","outlinePath","pt","sx","sy","toFixed","maxWidth","fill","stroke","strokeWidth","idx","col","row","floor","localX","localY","cellW","cellH","color","rect","data-cell","fillOpacity","badgeVariants","Badge","Table","div","table","TableHeader","thead","TableBody","tbody","TableRow","tr","TableHead","th","TableCell","td","formatNumber","digits","ONDIEPSTE_INCL_UITLEG","formatPercent","VeranderingTovVorige","delta","vorigeDatum","toLocaleDateString","span","data-testid","title","VakOverviewTable","selectedCode","onSelect","sortKey","setSortKey","sortDir","setSortDir","sortedRows","dir","code","dataset","survey_date","av","bv","toggleSort","SortableHeader","label","colKey","align","ds","isSelected","ngdM3","volume_above_ngd_m3","ngdAfgerond","round","data-selected","onClick","PeildatumCell","date","gemeten","surface_ratio_measured","delta_volume_above_ngd_m3","vorige_peiling_datum","volume_above_ohd_m3","delta_volume_above_ohd_m3","surface_ratio_above_ngd","surface_ratio_below_ohd","CmBadge","cm","cm_above_ngd","verouderd","isPeilingVerouderd","depth_max_in_vak","depth_max_incl_randstrook","heatmap_cells","Icon","ArrowUp","ArrowDown","ArrowUpDown","cmAfgerond","cmBadgeKlasse","PEILING_VEROUDERD_DAGEN","dag","now","getTime","GEMETEN_MELDEN_ONDER","PEILSOORT_LABELS","regulier","atp","voorspelling","ATP_OUTLINE_COLOR","isAtp","peilsoort","COLUMNS","csvLabel","kind","verandering","alleenCsv","alignOf","formatCell","formatDate","String","PEILSOORT_FILTERS","KuberingTable","datasets","vakCodes","peilsoortFilter","setPeilsoortFilter","vakSet","baggervak_code","mul","compareValues","aantalPerSoort","telling","schermKolommen","c","filterBalk","ToggleGroup","onValueChange","ToggleGroupItem","csv","filename","blob","Blob","URL","createObjectURL","anchor","createElement","href","download","appendChild","click","remove","revokeObjectURL","triggerCsvDownload","csvCell","toCsv","disabled","Download","SortIndicator","active","apiDatasetsList","apiDatasetsSoftDeleteCreate","PERIODES","KLEUR","periodeLabel","start","periode","month","year","dagMaand","day","ongeveer","m3","stap","VakBaggerhoeveelheden","setPeriode","bezig","fout","setData","setBezig","setFout","afgebroken","vak","antwoord","catch","finally","useBaggerhoeveelheden","verwachting","useBaggerverwachting","punten","chartData","punt","volume","volume_m3","reizen","maand","mediaan_m3","band","laag_m3","hoog_m3","staven","Melding","ResponsiveContainer","BarChart","CartesianGrid","strokeDasharray","XAxis","dataKey","fontSize","YAxis","angle","position","Tooltip","formatter","naam","Bar","stackId","isAnimationActive","ErrorBar","direction","Toelichting","grootheid_label","totaal","totaal_m3","zonderVolume","zonder_volume","Verwachtingsregel","data_tot","reden","PanamaxHull","xScale","yScale","centerX","showLabel","halfBeam","PANAMAX_BEAM","keel","px","py","PANAMAX_BOTTOM","g","x1","x2","y1","y2","text","textAnchor","REF_LEVEL_COLORS","ngd","ivd","ohd","REF_LEVEL_LABELS","lat","NAP_COLOR","VakCrossSection","samples","referenceLevels","margin","innerW","innerH","distance_m","xMin","xMax","depths","depth_m","refValues","yMin","yMinAll","PANAMAX_FREEBOARD","seabedPath","Boolean","defs","linearGradient","stop","offset","stopColor","stopOpacity","level","data-ref-line","tspan","kaartExperiment","allowed","fallback","window","location","hostname","localStorage","getItem","rasterResolutionFactor","MAX_EVENTS","gebeurtenissen","interacties","onderweg","generatie","weggelaten","tellers","startTijd","tijdlijnMetingAan","aan","ingeschakeld","bewaar","shift","logTijdlijn","details","startMs","performance","startMeting","run","begin","next","afgerond","duurMs","t","teller","maxMs","fouten","tel","meet","werk","resultaat","diagnoses","registreerDiagnose","fn","__tijdlijn","snapshot","nu","versie","verstrekenMs","fromEntries","wachtMs","reset","clear","rapport","verstreken","regels","meetpunt","log","PerformanceObserver","supportedEntryTypes","list","entry","getEntries","startTime","entryType","duration","resource","api","origin","pathname","cog","detailsBeschikbaar","responseStart","timingDetails","ttfbMs","requestStart","downloadMs","responseEnd","transferBytes","transferSize","observe","retentionStartFromMonths","firstOfMonth","months","setMonth","getMonth","getFullYear","padStart","NL_MONTHS_SHORT","NL_MONTHS_FULL","monthYear","monthOnly","formatRetentionRange","selectedFirstOfMonth","end","formatMaandKort","seedSasCache","cog_url","sasCache","fetchedAt","displaySasCache","display_cog_url","vorigeUrl","vorige_display_cog_url","vorige_peiling_id","endOfMonth","laatsteDag","UTC","getUTCDate","voorspellingsDatums","isVoorspellingsDatum","datasetsCache","datasetTimes","DATASET_TTL_MS","datasetsInFlight","datasetsCacheKey","retentionMonths","fetchDatasets","surveyDate","cached","existing","queryDateTo","metingQuery","date_from","date_to","latest_per_vak","slim","include_cog_url","limit","promise","metingen","voorspellingen","all","results","vakken","aanvulling","useReadyDatasets","setDatasets","loading","setLoading","atpDatasetsCache","atpDatasetTimes","atpDatasetsInFlight","useAtpDatasets","fetchAtpDatasets","sasInFlight","SAS_TTL_MS","getCogUrl","datasetId","apiDatasetsCogUrlRetrieve","display_url","getDisplayCogUrl","original","refreshed","hexToRgb","hex","n","VERANDERING_STOPS","meter","rgb","veranderingToColor","stops","laag","hoog","veranderingPositie","tileBboxMercator","z","pow","minX","maxY","EARTH_CIRCUMFERENCE_M","TILE_SIZE","__prepareC5Tile","ref","getReferencePairing","pixels","deltaTileImageData","canvas","OffscreenCanvas","context","getContext","putImageData","convertToBlob","bitmap","createImageBitmap","clearRect","drawImage","close","decoded","getImageData","digest","crypto","subtle","rgbaHash","Uint8Array","toString","bytes","arrayBuffer","binary","byte","fromCharCode","png","geotiffCache","metadataCache","referencePairing","setReferencePairing","bathyCogUrl","refCogUrl","clearReferencePairing","deltaColormap","deltaVerschil","spreidingPerUrl","setSpreiding","sigma","getSpreiding","pool","getPool","Pool","decode","bind","directory","TILE_BYTES","CACHE_BUDGET","achtste","deviceMemory","cacheBudgetBytes","REFERENCE_CACHE_BYTES","BLOCK_CACHE_BYTES","RENDER_CACHE_BYTES","decodedBlocks","ready","pending","constructor","maxBytes","load","this","block","byteLength","oldest","keys","attach","tiff","getImage","images","index","image","readBlock","getTileOrStrip","sample","decoder","LEVEND_CACHE_MAX","VOORUIT_CACHE_MAX","referenceTiles","referenceReads","referenceBytes","levendeTiles","vooruitTiles","lopendeRenders","verversPositie","cache","snoei","oudste","deltaTileCacheGet","levend","klaargezet","getGeoTiff","fromUrl","getCogMeta","gdalMeta","getGDALMetadata","SCALE","parseFloat","OFFSET","noData","getGDALNoData","readRawTile","fillValue","Infinity","readRasters","bbox","interleave","resampleMethod","leesVenster","breedte","hoogte","ruw","Float64Array","isNoData","NaN","readReferenceTile","tile","isFinite","stand","verschil","colormap","huidigeKleurstand","cacheKey","cacheSleutel","cachedTile","lopend","beeld","render","bathyRaw","refRaw","bathyMeta","refMeta","rgba","Uint8ClampedArray","kans","meetSync","rawB","rawR","depth","reference","kansToColor","kansBoven","differenceToColor","imageData","ImageData","deltaTileCacheSet","renderNaarCache","groepeer","sleutel","bathy","tegel","proj4","RD_TO_WGS84","rdToWgs84","forward","transformGeoJSON","geojson","features","feature","geometry","transformGeometry","transformCoord","lng","AARDSTRAAL","binnen","ringen","raak","xa","ya","xb","yb","meetKleur","diepte","bereik","depthToColor","mode","BREEDTE","VakMeting","canvasRef","useRef","setStand","kader","ringenRd","hoekGraden","hoek","mx","reduce","my","lokaal","x0","y0","ox","oy","ly","lx","vakKader","inverse","wgs84ToRd","verhouding","schaal","afb","rdNaarMerc","tan","naarMercator","lokaalNaarRd","p00","p10","p01","dxi","dyi","dxj","dyj","pixelNaarMercator","hoeken","mercatorKader","pixelM","hypot","vb","ceil","vh","venster","dieptes","Float32Array","kol","rij","geldig","q","kwantiel","midden","kleurBereik","current","createImageData","strokeStyle","lineWidth","beginPath","moveTo","lineTo","closePath","Legenda","verloop","from","_","stappen","ngdPositie","background","left","procent","deel","bestemmingRijen","bestemmingen","binnen_m3","rand","rand_m3","buiten","herkomstRijen","polygoon","herkomst","h","samenvatting","eigen","elders","laatste","VakWerkgebied","useWerkgebied","dekking","Werkgebied","van","tot","section","h4","reizen_met_track","volume_met_track_m3","Verdeling","rijen","rand_m","ul","li","Card","CardHeader","CardTitle","CardContent","THRESHOLDS","STATUS_LEVELS","statusColor","thresholds","amber","red","STATUS_CELL_CLASSES","green","VakDetailPanel","levels","latestDataset","trends","peilingen","h2","KpiCard","sub","cross_section_samples","Link","to","MapPin","Ruler","NAAST_DE_GEUL","VEROUDERD_VLAK","lijnKleur","lijnTekstKleur","LEGENDA","cmTekst","maandJaar","isVerouderd","peildatum","waardeLabel","VakkenLijn","lijn","los","kolommen","gridTemplateColumns","naastIndex","losStart","losGekozen","losVerouderd","kleur","aria-hidden","gekozen","button","aria-pressed","gridColumn","THEMES","dark","ChartContext","React","ChartContainer","uniqueId","chartId","data-chart","ChartStyle","RechartsPrimitive","colorConfig","theme","dangerouslySetInnerHTML","__html","prefix","itemConfig","ChartTooltip","ChartTooltipContent","payload","indicator","hideLabel","hideIndicator","labelFormatter","labelClassName","nameKey","labelKey","useChart","tooltipLabel","item","getPayloadConfigFromPayload","nestLabel","indicatorColor","payloadPayload","configLabelKey","TIJD_KEY","tijdVanDatum","STAPPEN_MAANDEN","RAND_MS","maandIndex","tijd","getUTCFullYear","getUTCMonth","vanMaandIndex","maakLabel","stapMaanden","timeZone","tijdas","tijden","maxStreepjes","domain","ticks","formatTick","eerste","aantalMaanden","datumLang","metTijd","dateKey","formatYTick","VolumeTrendChart","yLabel","unit","chartConfig","cfg","tijdData","as","ComposedChart","top","right","bottom","tickFormatter","content","formatTooltipValue","Line","dashed","dot","activeDot","connectNulls","DepthTimeChart","bands","showWaterFill","autoYDomain","scan","record","minKey","maxKey","pad","yDomain","setYDomain","onWheel","preventDefault","yMax","range","deltaY","newMin","newMax","ReferenceArea","ifOverflow","strokeOpacity","ReferenceLine","useBaggervakken","setGeojson","setError","fc","transformed","useLatestDatasets","refreshKey","latestByVak","VOLUME_KEYS","description","h1","MODE_LABELS","shallowest","shallowest_incl","mean","VOLUME_TYPE_LABELS","surface","LIJNKLEUR","AnalysePage","vakLoading","setMode","volumeType","setVolumeType","overviewLoading","searchParams","useSearchParams","setSelectedCode","bestCode","bestValue","selectedVakken","dsLoading","setVoorspellingen","vakCodesKey","baggervak","alle","useTrendDatasets","overviewRows","f","properties","obb_angle_deg","obb_width_m","obb_height_m","vakkenLijn","order","maakVakkenLijn","selectedRow","ngd_level","ohd_level","byDate","depth_mean_in_vak","depth_min_in_vak","surface_above_ngd_m2","volume_above_ivd_m3","surface_above_ivd_m2","surface_above_ohd_m2","pivotDatasets","volumeData","records","aanloop","veld","withVoorspelling","volumeLines","suffix","metrics","metric","laden","PageHeader","PageSection","Input","input","Separator","orientation","decorative","SeparatorPrimitive","Sheet","SheetPrimitive","SheetPortal","SheetOverlay","SheetContent","side","showCloseButton","XIcon","SheetHeader","SheetTitle","SheetDescription","Skeleton","TooltipProvider","delayDuration","TooltipPrimitive","TooltipTrigger","TooltipContent","sideOffset","SidebarContext","useSidebar","SidebarProvider","defaultOpen","open","openProp","onOpenChange","setOpenProp","isMobile","setIsMobile","mql","matchMedia","onChange","useIsMobile","openMobile","setOpenMobile","_open","_setOpen","setOpen","openState","toggleSidebar","handleKeyDown","metaKey","ctrlKey","state","contextValue","Sidebar","collapsible","data-sidebar","data-mobile","data-state","data-collapsible","data-side","SidebarTrigger","PanelLeftIcon","SidebarInset","main","SidebarHeader","SidebarFooter","SidebarContent","SidebarGroup","SidebarGroupLabel","SidebarGroupContent","SidebarMenu","SidebarMenuItem","sidebarMenuButtonVariants","SidebarMenuButton","isActive","tooltip","data-active","hidden","MAIN_ITEMS","IconMap2","IconRipple","IconChartLine","requiresAnalyseAccess","IconArrowWaveRightUp","BEHEER_ITEMS","IconUpload","IconDatabase","IconClock","IconTrash","SidebarNavList","items","beheer","useLocation","NavLink","AppSidebar","mainItems","has_analyse_access","initialen","eigenNaam","display","toUpperCase","gebruikersnaam","img","src","logo","alt","mark","nav","LogOut","Toaster","useTheme","Sonner","icons","success","CircleCheckIcon","info","InfoIcon","warning","TriangleAlertIcon","OctagonXIcon","Loader2Icon","ZandstromenPage","lazy","ZandstromenFoutgrens","Component","getDerivedStateFromError","componentDidCatch","componentStack","opnieuw","reload","Label","LabelPrimitive","Select","SelectPrimitive","SelectValue","SelectTrigger","ChevronDownIcon","SelectContent","SelectScrollUpButton","SelectScrollDownButton","SelectItem","CheckIcon","ChevronUpIcon","STATUS_LABELS","uploaded","validating","validated","processing","published","archived","deleted","SortableHead","activeKey","activeDir","onSort","JOB_STATUS_LABELS","running","failure","JOB_TYPE_LABELS","extract","convert","statusBadge","idle","uploading","confirming","UploadQueue","uploads","onClear","file","progress","DropZone","dragOver","onDragOver","onDragLeave","onDrop","initialState","peilsoorten","ondiepstePunten","dieptecijfers","dieptecontouren","baggervakken","vaargeul","randstroken","passeerstroken","taluds","stortlocaties","scheepsactiviteit","omgeving","relief","laagfouten","laagmeldingen","referenceLevel","selectedVak","selectie","selectedDate","availableDates","clipToBaggervakken","is3D","omgevingKaarten","crossSection","drawing","gebied","tijdlijn","geladenDatums","ladendeDatums","geselecteerdBezig","achtergrondBezig","reducer","action","layer","melding","dates","KaartContext","KaartProvider","dispatch","useReducer","useKaart","toontPeilsoort","useOverlays","layerType","layer_type","inFlight","getReferenceCogUrl","baggervakCode","sampleCrossSection","cogUrl","intervalM","startLng","startLat","endLng","endLat","totalDistance","haversineDistance","numSamples","points","distance","locationValues","latitude","longitude","raw","pointInRing","inside","xi","yi","xj","yj","pointInFeature","poly","lat1","lon1","lat2","lon2","dLat","dLon","R","atan2","sqrt","LEVELS","resolveBaggervak","cogCode","scalars","ivd_level","resolveOverlay","inner","sampleReferenceLevelsAlongLine","overlays","resolved","codes","urlByCodeLevel","flatMap","perLevelValues","scalar","MIN_HEIGHT","PanamaxHullRechartsAdapter","xAxisMap","yAxisMap","xAxis","yAxis","atpDepth","CrossSectionDrawer","atpDatasets","mapRef","depthData","setDepthData","setHeight","ladeRef","kolom","setKolom","maxHoogte","begrens","kolomNu","parentElement","clientHeight","heeftLijn","ouder","waarnemer","ResizeObserver","disconnect","onSleepStart","clientY","target","setPointerCapture","pointerId","onSleep","onSleepEinde","frame","requestAnimationFrame","resize","cancelAnimationFrame","vaargeulOverlays","passeerstrookOverlays","randstrookOverlays","taludOverlays","depthOverlays","collections","referenceByIndex","setReferenceByIndex","result","activeAtpDatasets","regularUrlsRaw","atpUrlsRaw","regularUrls","atpUrls","regularSeries","atpSeries","regularMerged","acc","series","atpOnlyDepth","merged","base","mergeAtpPriority","yZoom","setYZoom","onChartWheel","lo","hi","mid","factor","newRange","resetZoom","filteredData","aria-orientation","aria-valuenow","aria-valuemin","aria-valuemax","tabIndex","onPointerDown","onPointerMove","onPointerUp","onPointerCancel","onKeyDown","Scissors","RotateCcw","X","Area","baseValue","Customized","component","MapToolbar","onGebiedStart","ToolbarButton","Hexagon","getMap","easeTo","pitch","bearing","Box","zoomIn","Plus","zoomOut","Minus","CrossSectionTool","startPoint","setStartPoint","getCanvas","cursor","handleClick","point","lngLat","handler","on","off","moveToTop","getLayer","moveLayer","lineGeoJson","coord","Source","Layer","paint","meters","opties","DiepteSpeld","reading","onSluit","anker","eronder","data-speld","data-anker","plek","SpeldInhoud","vakCode","depthNap","referenceNap","deltaM","onzekerheid","formatOppervlak","m2","formatDiepte","GEBIED_KLEUR","GebiedTool","vastgelegdeRing","ringRef","setRingState","setCursor","zetRing","afronden","dichtbij","pa","project","pb","opKlik","huidige","opDubbelklik","opBeweging","opToets","naarBoven","gebiedGeoJSON","markerGeoJson","gebiedMarkerGeoJSON","shallowest_point","depth_max","layout","OmgevingLagen","tik","isGeladen","beforeId","vooruitladen","zichtbaar","setZichtbaar","setVooruit","montage","wissel","heeftKaarten","w","montageIds","ids","every","erbij","delay","getalOfNull","tekstOfNull","vakVanOverlay","overlayObject","niveaus","oppervlakM2","area_m2","omschrijving","collectie","objectVoorPunt","volgorde","plekNaam","toLowerCase","LEEG_READING","LEEG","useDieptecijfers","uitkomsten","setUitkomsten","setLaden","mijn","uitkomst","perPixelReferentie","per_pixel_referentie","afgekapt","haalCijfers","resultaten","boven","dieptecijfersGeoJSON","zonderReferentieRaster","heeftContouren","maakContourLabel","niveau","heel","CONTOUR_KLEUR","useDieptecontouren","interval","actief","lijnen","haalContouren","dieptecontourenGeoJSON","mercatorToLngLat","atan","exp","datasetsInBeeld","bounds","marge","bbox_minx","bbox_miny","bbox_maxx","bbox_maxy","minx","miny","maxx","maxy","metMarge","bboxInBeeld","datumsOpAfstand","datums","hier","indexOf","na","voor","PREPARED_TILES","laagSleutel","refLevel","maandVenster","tweedeCogUrl","dekt","oplossing","opgelost","resolvedAt","retention","catalogus","vensterVoorPeiling","geselecteerd","terug","setUTCDate","haalVenster","bewaard","belofte","gegevens","onthoud","mercY","lngLatToTilePixel","mNorth","mSouth","computeFeatureBbox","minLng","minLat","maxLng","maxLat","visit","coords","bboxOverlaps","LRUCache","maxSize","first","BaggervakMask","featureBboxes","getMask","getTile","mask","getClipCanvas","clipCanvas","getFeature","rasterize","sinh","tileBounds","fb","fi","fillStyle","drawPolygon","clipMask","setClipMask","getClipMask","applyClipToTile","globalCompositeOperation","cogSourceUrl","sasUrl","clip","heeftColormapKeuze","walkCoordinates","child","LAAG_KLEUR","TEGEL","ZENIT","AZIMUT","VLAK","FASE_VOLGORDE","FASE_KLEUR","baggeren","lossen","varen","onbekend","FASE_DEKKING","FASE_LABELS","STRIJDIG_STREEP","SHALLOWEST_MARKER_COLORS","shallowestColorExpression","VOORKEUR","speldAnker","obstakel","vlak","links","LABEL_BREEDTE","rechts","onder","labelVlak","raakt","tileQueue","backgroundActive","foreground","foregroundReady","backgroundLimit","setPlan","previous","task","drain","schedule","work","reject","DOMException","once","backgroundQueued","queued","foregroundQueued","splice","cogFouten","TILE_URL_RE","cogUrlHash","groupOne","extractCogUrl","foutnaam","renderCog","minY","maxX","pixel","celMeter","overdrijving","dzdx","dzdy","helling","richting","schaduw","middenY","breedtegraad","grondmaatPixel","renderReliefTile","refUrl","renderDeltaTile","cogProtocol","natural","minzoom","maplibregl","controller","wacht","displayTilesProtocol","INITIAL_VIEW_STATE","didAutoFitBounds","SCHEEPSACTIVITEIT_VERF","MapView","onObjectClick","isMapLoaded","setIsMapLoaded","benchmarkState","leesState","gl","debug","getExtension","renderer","getParameter","UNMASKED_RENDERER_WEBGL","tileSources","getStyle","getSource","maxzoom","tilesLoaded","areTilesLoaded","setView","center","jumpTo","getCenter","toArray","getZoom","preparedSourceCount","cogSources","displayCogSourceCount","__kaartBenchmark","registreerKaartBenchmark","mapError","setMapError","atpCogUrls","setAtpCogUrls","baggervakkenLoading","vaargeulLoading","randstrokenLoading","passeerstrokenLoading","taludsLoading","stortlocatiesLoading","scheepsactiviteitFout","scheepsactiviteitMelding","setMelding","toon","aantalFeatures","vanaf","totEnMet","dekkingsmelding","gestopt","buur","buurvenster","useScheepsactiviteit","ondiepsteGeoJson","cmAboveNgd","shallowestPointsGeoJSON","cijferReferentie","dieptecijfersGeoJson","dieptecijfersLaden","contourenGeoJson","contourenLaden","dieptecijfersMelding","contourenMelding","omgevingVariant","omgevingDoel","omgevingBronGeladen","isSourceLoaded","omgevingLezing","diepteUrl","diepte_url","zekerheidUrl","zekerheid_url","speld","setSpeld","vormen","speldReading","setReading","achterhaald","leesPeiling","waarden","rauw","vanPeiling","extra","sigmaCm","bronIndex","leesOmgeving","vorigeId","vorige","latLevel","lat_level","strokenVoorReferentie","readDepthAtPoint","useDiepteOpPunt","kanSpeldLezen","tekent","defaultPrevented","closest","mapStyle","shapesLoading","collection","geojsonBounds","fitBounds","padding","maxZoom","vakRefLookup","spreiding_maand_m","separatorGereed","setSeparatorGereed","aanwezig","setBeeld","leesBeeld","getBounds","getWest","getSouth","getEast","getNorth","vorig","idleTik","setIdleTik","beweegt","setBeweegt","toestemming","setToestemming","alles","laadContext","completionFrame","sourceComplete","sourceId","maandLagen","huidigeDatum","metadataVooruitladen","perDatum","setPerDatum","known","preparedTiles","tileUrls","revision","setRevision","bronContext","isPakket","setMontage","actieveDatumRef","epoch","jobs","failures","laatstGestarteTik","sleutelNu","configSleutel","old","pakketDatums","actueel","toegelaten","monteer","oud","jobKey","kanStarten","attempts","retryAt","mount","mislukt","isGestopt","inBeeld","refs","dsConfig","configForReferenceLevel","setColorFunction","makeColorFunction","display_tile_urls","resolveerMaand","backgroundRunning","job","volgende","lagen","isActueel","actieveDatum","unie","datumSleutels","actieveSleutels","klareDatums","bezigMetKlaarzetten","useMaandLagen","actieveLagen","l","maandDiagnoseRef","lees","maanden","selectieOpgelost","actiefGeladen","maandenOpgelost","maandenTotaal","stijlGeladen","isStyleLoaded","alleTegelsGeladen","afmelden","laatsteStatus","renderGemeld","neemStatus","detail","actieveBronnen","wachtendeBronnen","opRender","opError","useLayoutEffect","gemeldeTijdlijnRef","urls","mislukteUrls","bronGeladen","bronMislukt","sleutels","leesTijdlijnStatus","handtekening","tryResolve","timeout","clearTimeout","demUrl","addSource","setTerrain","exaggeration","removeSource","originalEvent","getContainer","kaartVlak","getBoundingClientRect","balk","ownerDocument","querySelector","clientWidth","atpBboxGeoJson","ll","lr","ur","initialViewState","keyboard","reuseMaps","interactiveLayerIds","onLoad","onError","Marker","NavigationControl","showZoom","visualizePitch","ScaleControl","Slider","defaultValue","_values","SliderPrimitive","RETENTION_OPTIONS","positie","tijdlijnLabels","breedtePx","isJanuari","isJaarZonderJanuari","kandidaten","teksten","bezet","labels","TimelineBar","laatsteMeting","setDates","setLaatsteMeting","at","useSurveyDates","playing","setPlaying","labelRij","labelBreedte","setLabelBreedte","currentIndex","goTo","clamped","indexRef","tag","tagName","heeftDatums","labelDatum","kop","dateLabel","formatMaandVoluit","allesGeladen","Pause","Play","ChevronLeft","ChevronRight","step","onValueCommit","laadtNu","halveStap","eind","opt","WerkbladGebied","h3","gemetenDekking","areaM2","polygonAreaM2","polygon_area_m2","Pencil","per_vak","Kerngetal","depth_mean","bijschrift","dl","Regel","depth_min","dataset_id","dt","dd","volgendeIndex","toets","REFERENTIE_NAAM","REFERENTIE_ZIN","BEELDEN","kort","COLORMAPS","legendaBalk","achtergrond","getLegendStops","harderVerloop","KANS_BINS","reverse","bins","DIFFERENCE_BINS","verschilPositie","kleuren","dieptestrepen","klassestrepen","grens","Strepen","strepen","Vakjes","bin","backgroundColor","isPeil","zin","isInteger","Uitleg","Keuze","onPeil","peil","kies","optie","ContourRegel","borderColor","PeilsoortRegel","OmgevingRegel","WerkbladKaartbeeld","onInklappen","kiesPeil","kiesBeeld","gekozenBeeld","aanvullend","ChevronsRight","currentTarget","querySelectorAll","focus","aria-checked","ONDERGRONDEN","DIEPTEVERLOOP","gestippeld","vulling","borderRadius","border","dikte","SYMBOOL","boxShadow","MEER_LAGEN","laagAan","WerkbladLagen","meer","onMeer","aanMeer","aantalAan","uitleg","LaagRegel","symbool","onWissel","Schakelaar","uitgeschakeld","aria-expanded","LaagMelding","ScheepsactiviteitMeldingen","metNaam","ScheepsactiviteitUitleg","fase","opacity","regel","WerkbladVak","laatstePeilingen","opDeKaart","Stortlocatie","eigenschappen","peiling","cmGetoond","analyse","eigenVlakken","datumKort","VerschilRegel","VLAKKEN","dagen","STAND_SLEUTEL","isBreedScherm","leesStand","basis","tab","meerLagen","opgeslagen","TABS","Icoon","Layers","SquareStack","Werkblad","zet","setPeil","idsSleutel","datasetIds","dataset_ids","useGebiedStatistiek","eersteKeer","verwachtingTekst","aside","StrookKnop","ChevronsLeft","gekozenTab","aria-selected","aria-controls","aria-labelledby","KaartPageInner","openOp","setGekozen","setItem","wijziging","useWerkbladStand","baggervakkenGeoJson","handleObjectClick","handleGebiedStart","cogUrlCache","COG_URL_TTL_MS","getPlaatmigratieCogUrl","runId","expiry","resp","getPlaatmigratieFilm","FilmVenster","titel","setUrl","video","controls","autoPlay","loop","muted","playsInline","Tijdbalk","jaren","meetjaren","metVoorspelling","speelt","onJaar","onSpeelt","onMetVoorspelling","pos","meetOpSchuif","Loader2","Switch","checked","onCheckedChange","htmlFor","STOPS","plaatmigratieKleurFunctie","PLAATMIGRATIE_MAX","t0","k0","t1","k1","plaatmigratieKleur","voorspellingsVlak","voorspeld","meetgebieden","laatste_meting","horizon","heeft_meting","GEEN_URLS","inVenster","bronId","PlaatmigratieLagen","alleJaren","onKlaar","useMap","staat","setStaat","bij","setBij","urlsRef","jaarRef","gemount","gewenst","paren","nieuweParen","voegUrlsToe","vensterOfBij","afspeelvolgorde","BEGIN","GEEN_VAKKEN","VAKKEN_LAAG","PlaatmigratiePage","leeg","setRun","setLeeg","usePlaatmigratie","film","setFilm","usePlaatmigratieFilm","filmOpen","setFilmOpen","stijl","setMetVoorspelling","zichtbareJaren","meetjarenVan","jaarKeuze","setJaarKeuze","setSpeelt","setKlaar","startStop","setDiepte","laatsteRead","voorladen","dichtstbijzijndeJaar","onMove","onMouseMove","onMouseLeave","Film","createBrowserRouter","loader","navigate","useNavigate","setEmail","setPassword","isSubmitting","setIsSubmitting","form","onSubmit","autoComplete","required","loginBg","hash","Navigate","Outlet","Analyse","Suspense","setSource","surveyQueue","setUploads","updateUpload","prev","addEntries","files","startUpload","newEntries","startIndex","clearDone","useUploadQueue","surveyDragOver","setSurveyDragOver","surveyFileRef","uploadSurveyFile","original_filename","surveyId","uploadUrl","xhr","XMLHttpRequest","setRequestHeader","upload","onprogress","pct","lengthComputable","loaded","total","onload","onerror","send","uploadToBlob","upload_url","apiSurveysConfirmUploadCreate","handleSurveyFiles","handleSurveyDrop","dataTransfer","placeholder","accept","multiple","baggervakFilter","setBaggervakFilter","statusFilter","setStatusFilter","handleSort","sortedDatasets","st","handleDelete","Trash2","useParams","setDataset","actionLoading","setActionLoading","editingDate","setEditingDate","dateValue","setDateValue","fetchDataset","handleDateSave","survey","surveyDateFormatted","metaRows","created","modified","rasterRows","resolution_x","resolution_y","Check","raw_blob_path","cog_blob_path","setJobs","fetchJobs","showLoading","hasRunning","job_type","progress_percent","error_message","purging","setPurging","fetchDeleted","confirm","Fase2ComingSoon","Sentry","RouterProvider","router","dsn","SENTRY_DSN","release","COMMIT_SHA","createRoot","App","SwitchPrimitive","indices","Range","protocol","metadata","DataView","AbortSignal","getUint32","TextDecoder","dataStart","throwIfAborted","tilejson","isSafeInteger","inputs","twMerge","clsx","toggleVariants","ToggleGroupContext","spacing","ToggleGroupPrimitive","data-spacing","DIEPTE_STANDAARD","ZAND_STOPS","ZAND_BEREIK_M","inDatum","r0","g0","b0","r1","g1","b1","last","interpolateColor","m0","m1","zandKleur","count","bovenkant","diff","differenceToRampColor","differenceToBinColor","SQRT2","erf","normaleVerdeling","referenceDepth","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","call","O","chunkIds","priority","notFulfilled","fulfilled","getter","__esModule","getPrototypeOf","obj","ns","def","getOwnPropertyNames","definition","defineProperty","enumerable","chunkId","promises","miniCssF","Function","prop","prototype","hasOwnProperty","script","needAttach","scripts","getElementsByTagName","getAttribute","charset","nc","setAttribute","onScriptComplete","doneFns","parentNode","removeChild","Symbol","toStringTag","nmd","paths","scriptUrl","importScripts","currentScript","installedChunks","installedChunkData","errorType","realSrc","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""}