{"version":3,"sources":["jsdelivr-header.js","/gh/hardiesoft/spectastiq@0.10.4/spectastiq.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,CCNA,WACE,aAEA,IAAI,EAA6C,oBAAb,SAA2B,SAAS,cAAgB,KACxF,MAAM,EAAU,CACd,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,KAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,MAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,KAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,MAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,OAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,UAGjB,EAAS,CACb,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,OAAU,QACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,OAAU,QACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,MAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,QACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,QAAU,QACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,MAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,UAEjB,EAAU,CACd,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,MAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,OACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,MAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,OAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,QACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,OAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACrB,CAAC,QAAU,QAAU,SACr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","file":"/gh/hardiesoft/spectastiq@0.10.4/spectastiq.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.48.0.\n * Original file: /gh/hardiesoft/spectastiq@0.10.4/spectastiq.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","(function () {\n  'use strict';\n\n  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;\n  const VIRIDIS = [\n    [0.267004, 0.004874, 0.329415],\n    [0.268510, 0.009605, 0.335427],\n    [0.269944, 0.014625, 0.341379],\n    [0.271305, 0.019942, 0.347269],\n    [0.272594, 0.025563, 0.353093],\n    [0.273809, 0.031497, 0.358853],\n    [0.274952, 0.037752, 0.364543],\n    [0.276022, 0.044167, 0.370164],\n    [0.277018, 0.050344, 0.375715],\n    [0.277941, 0.056324, 0.381191],\n    [0.278791, 0.062145, 0.386592],\n    [0.279566, 0.067836, 0.391917],\n    [0.280267, 0.073417, 0.397163],\n    [0.280894, 0.078907, 0.402329],\n    [0.281446, 0.084320, 0.407414],\n    [0.281924, 0.089666, 0.412415],\n    [0.282327, 0.094955, 0.417331],\n    [0.282656, 0.100196, 0.422160],\n    [0.282910, 0.105393, 0.426902],\n    [0.283091, 0.110553, 0.431554],\n    [0.283197, 0.115680, 0.436115],\n    [0.283229, 0.120777, 0.440584],\n    [0.283187, 0.125848, 0.444960],\n    [0.283072, 0.130895, 0.449241],\n    [0.282884, 0.135920, 0.453427],\n    [0.282623, 0.140926, 0.457517],\n    [0.282290, 0.145912, 0.461510],\n    [0.281887, 0.150881, 0.465405],\n    [0.281412, 0.155834, 0.469201],\n    [0.280868, 0.160771, 0.472899],\n    [0.280255, 0.165693, 0.476498],\n    [0.279574, 0.170599, 0.479997],\n    [0.278826, 0.175490, 0.483397],\n    [0.278012, 0.180367, 0.486697],\n    [0.277134, 0.185228, 0.489898],\n    [0.276194, 0.190074, 0.493001],\n    [0.275191, 0.194905, 0.496005],\n    [0.274128, 0.199721, 0.498911],\n    [0.273006, 0.204520, 0.501721],\n    [0.271828, 0.209303, 0.504434],\n    [0.270595, 0.214069, 0.507052],\n    [0.269308, 0.218818, 0.509577],\n    [0.267968, 0.223549, 0.512008],\n    [0.266580, 0.228262, 0.514349],\n    [0.265145, 0.232956, 0.516599],\n    [0.263663, 0.237631, 0.518762],\n    [0.262138, 0.242286, 0.520837],\n    [0.260571, 0.246922, 0.522828],\n    [0.258965, 0.251537, 0.524736],\n    [0.257322, 0.256130, 0.526563],\n    [0.255645, 0.260703, 0.528312],\n    [0.253935, 0.265254, 0.529983],\n    [0.252194, 0.269783, 0.531579],\n    [0.250425, 0.274290, 0.533103],\n    [0.248629, 0.278775, 0.534556],\n    [0.246811, 0.283237, 0.535941],\n    [0.244972, 0.287675, 0.537260],\n    [0.243113, 0.292092, 0.538516],\n    [0.241237, 0.296485, 0.539709],\n    [0.239346, 0.300855, 0.540844],\n    [0.237441, 0.305202, 0.541921],\n    [0.235526, 0.309527, 0.542944],\n    [0.233603, 0.313828, 0.543914],\n    [0.231674, 0.318106, 0.544834],\n    [0.229739, 0.322361, 0.545706],\n    [0.227802, 0.326594, 0.546532],\n    [0.225863, 0.330805, 0.547314],\n    [0.223925, 0.334994, 0.548053],\n    [0.221989, 0.339161, 0.548752],\n    [0.220057, 0.343307, 0.549413],\n    [0.218130, 0.347432, 0.550038],\n    [0.216210, 0.351535, 0.550627],\n    [0.214298, 0.355619, 0.551184],\n    [0.212395, 0.359683, 0.551710],\n    [0.210503, 0.363727, 0.552206],\n    [0.208623, 0.367752, 0.552675],\n    [0.206756, 0.371758, 0.553117],\n    [0.204903, 0.375746, 0.553533],\n    [0.203063, 0.379716, 0.553925],\n    [0.201239, 0.383670, 0.554294],\n    [0.199430, 0.387607, 0.554642],\n    [0.197636, 0.391528, 0.554969],\n    [0.195860, 0.395433, 0.555276],\n    [0.194100, 0.399323, 0.555565],\n    [0.192357, 0.403199, 0.555836],\n    [0.190631, 0.407061, 0.556089],\n    [0.188923, 0.410910, 0.556326],\n    [0.187231, 0.414746, 0.556547],\n    [0.185556, 0.418570, 0.556753],\n    [0.183898, 0.422383, 0.556944],\n    [0.182256, 0.426184, 0.557120],\n    [0.180629, 0.429975, 0.557282],\n    [0.179019, 0.433756, 0.557430],\n    [0.177423, 0.437527, 0.557565],\n    [0.175841, 0.441290, 0.557685],\n    [0.174274, 0.445044, 0.557792],\n    [0.172719, 0.448791, 0.557885],\n    [0.171176, 0.452530, 0.557965],\n    [0.169646, 0.456262, 0.558030],\n    [0.168126, 0.459988, 0.558082],\n    [0.166617, 0.463708, 0.558119],\n    [0.165117, 0.467423, 0.558141],\n    [0.163625, 0.471133, 0.558148],\n    [0.162142, 0.474838, 0.558140],\n    [0.160665, 0.478540, 0.558115],\n    [0.159194, 0.482237, 0.558073],\n    [0.157729, 0.485932, 0.558013],\n    [0.156270, 0.489624, 0.557936],\n    [0.154815, 0.493313, 0.557840],\n    [0.153364, 0.497000, 0.557724],\n    [0.151918, 0.500685, 0.557587],\n    [0.150476, 0.504369, 0.557430],\n    [0.149039, 0.508051, 0.557250],\n    [0.147607, 0.511733, 0.557049],\n    [0.146180, 0.515413, 0.556823],\n    [0.144759, 0.519093, 0.556572],\n    [0.143343, 0.522773, 0.556295],\n    [0.141935, 0.526453, 0.555991],\n    [0.140536, 0.530132, 0.555659],\n    [0.139147, 0.533812, 0.555298],\n    [0.137770, 0.537492, 0.554906],\n    [0.136408, 0.541173, 0.554483],\n    [0.135066, 0.544853, 0.554029],\n    [0.133743, 0.548535, 0.553541],\n    [0.132444, 0.552216, 0.553018],\n    [0.131172, 0.555899, 0.552459],\n    [0.129933, 0.559582, 0.551864],\n    [0.128729, 0.563265, 0.551229],\n    [0.127568, 0.566949, 0.550556],\n    [0.126453, 0.570633, 0.549841],\n    [0.125394, 0.574318, 0.549086],\n    [0.124395, 0.578002, 0.548287],\n    [0.123463, 0.581687, 0.547445],\n    [0.122606, 0.585371, 0.546557],\n    [0.121831, 0.589055, 0.545623],\n    [0.121148, 0.592739, 0.544641],\n    [0.120565, 0.596422, 0.543611],\n    [0.120092, 0.600104, 0.542530],\n    [0.119738, 0.603785, 0.541400],\n    [0.119512, 0.607464, 0.540218],\n    [0.119423, 0.611141, 0.538982],\n    [0.119483, 0.614817, 0.537692],\n    [0.119699, 0.618490, 0.536347],\n    [0.120081, 0.622161, 0.534946],\n    [0.120638, 0.625828, 0.533488],\n    [0.121380, 0.629492, 0.531973],\n    [0.122312, 0.633153, 0.530398],\n    [0.123444, 0.636809, 0.528763],\n    [0.124780, 0.640461, 0.527068],\n    [0.126326, 0.644107, 0.525311],\n    [0.128087, 0.647749, 0.523491],\n    [0.130067, 0.651384, 0.521608],\n    [0.132268, 0.655014, 0.519661],\n    [0.134692, 0.658636, 0.517649],\n    [0.137339, 0.662252, 0.515571],\n    [0.140210, 0.665859, 0.513427],\n    [0.143303, 0.669459, 0.511215],\n    [0.146616, 0.673050, 0.508936],\n    [0.150148, 0.676631, 0.506589],\n    [0.153894, 0.680203, 0.504172],\n    [0.157851, 0.683765, 0.501686],\n    [0.162016, 0.687316, 0.499129],\n    [0.166383, 0.690856, 0.496502],\n    [0.170948, 0.694384, 0.493803],\n    [0.175707, 0.697900, 0.491033],\n    [0.180653, 0.701402, 0.488189],\n    [0.185783, 0.704891, 0.485273],\n    [0.191090, 0.708366, 0.482284],\n    [0.196571, 0.711827, 0.479221],\n    [0.202219, 0.715272, 0.476084],\n    [0.208030, 0.718701, 0.472873],\n    [0.214000, 0.722114, 0.469588],\n    [0.220124, 0.725509, 0.466226],\n    [0.226397, 0.728888, 0.462789],\n    [0.232815, 0.732247, 0.459277],\n    [0.239374, 0.735588, 0.455688],\n    [0.246070, 0.738910, 0.452024],\n    [0.252899, 0.742211, 0.448284],\n    [0.259857, 0.745492, 0.444467],\n    [0.266941, 0.748751, 0.440573],\n    [0.274149, 0.751988, 0.436601],\n    [0.281477, 0.755203, 0.432552],\n    [0.288921, 0.758394, 0.428426],\n    [0.296479, 0.761561, 0.424223],\n    [0.304148, 0.764704, 0.419943],\n    [0.311925, 0.767822, 0.415586],\n    [0.319809, 0.770914, 0.411152],\n    [0.327796, 0.773980, 0.406640],\n    [0.335885, 0.777018, 0.402049],\n    [0.344074, 0.780029, 0.397381],\n    [0.352360, 0.783011, 0.392636],\n    [0.360741, 0.785964, 0.387814],\n    [0.369214, 0.788888, 0.382914],\n    [0.377779, 0.791781, 0.377939],\n    [0.386433, 0.794644, 0.372886],\n    [0.395174, 0.797475, 0.367757],\n    [0.404001, 0.800275, 0.362552],\n    [0.412913, 0.803041, 0.357269],\n    [0.421908, 0.805774, 0.351910],\n    [0.430983, 0.808473, 0.346476],\n    [0.440137, 0.811138, 0.340967],\n    [0.449368, 0.813768, 0.335384],\n    [0.458674, 0.816363, 0.329727],\n    [0.468053, 0.818921, 0.323998],\n    [0.477504, 0.821444, 0.318195],\n    [0.487026, 0.823929, 0.312321],\n    [0.496615, 0.826376, 0.306377],\n    [0.506271, 0.828786, 0.300362],\n    [0.515992, 0.831158, 0.294279],\n    [0.525776, 0.833491, 0.288127],\n    [0.535621, 0.835785, 0.281908],\n    [0.545524, 0.838039, 0.275626],\n    [0.555484, 0.840254, 0.269281],\n    [0.565498, 0.842430, 0.262877],\n    [0.575563, 0.844566, 0.256415],\n    [0.585678, 0.846661, 0.249897],\n    [0.595839, 0.848717, 0.243329],\n    [0.606045, 0.850733, 0.236712],\n    [0.616293, 0.852709, 0.230052],\n    [0.626579, 0.854645, 0.223353],\n    [0.636902, 0.856542, 0.216620],\n    [0.647257, 0.858400, 0.209861],\n    [0.657642, 0.860219, 0.203082],\n    [0.668054, 0.861999, 0.196293],\n    [0.678489, 0.863742, 0.189503],\n    [0.688944, 0.865448, 0.182725],\n    [0.699415, 0.867117, 0.175971],\n    [0.709898, 0.868751, 0.169257],\n    [0.720391, 0.870350, 0.162603],\n    [0.730889, 0.871916, 0.156029],\n    [0.741388, 0.873449, 0.149561],\n    [0.751884, 0.874951, 0.143228],\n    [0.762373, 0.876424, 0.137064],\n    [0.772852, 0.877868, 0.131109],\n    [0.783315, 0.879285, 0.125405],\n    [0.793760, 0.880678, 0.120005],\n    [0.804182, 0.882046, 0.114965],\n    [0.814576, 0.883393, 0.110347],\n    [0.824940, 0.884720, 0.106217],\n    [0.835270, 0.886029, 0.102646],\n    [0.845561, 0.887322, 0.099702],\n    [0.855810, 0.888601, 0.097452],\n    [0.866013, 0.889868, 0.095953],\n    [0.876168, 0.891125, 0.095250],\n    [0.886271, 0.892374, 0.095374],\n    [0.896320, 0.893616, 0.096335],\n    [0.906311, 0.894855, 0.098125],\n    [0.916242, 0.896091, 0.100717],\n    [0.926106, 0.897330, 0.104071],\n    [0.935904, 0.898570, 0.108131],\n    [0.945636, 0.899815, 0.112838],\n    [0.955300, 0.901065, 0.118128],\n    [0.964894, 0.902323, 0.123941],\n    [0.974417, 0.903590, 0.130215],\n    [0.983868, 0.904867, 0.136897],\n    [0.993248, 0.906157, 0.143936],\n  ];\n\n  const PLASMA = [\n    [0.050383, 0.029803, 0.527975],\n    [0.063536, 0.028426, 0.533124],\n    [0.075353, 0.027206, 0.538007],\n    [0.086222, 0.026125, 0.542658],\n    [0.096379, 0.025165, 0.547103],\n    [0.105980, 0.024309, 0.551368],\n    [0.115124, 0.023556, 0.555468],\n    [0.123903, 0.022878, 0.559423],\n    [0.132381, 0.022258, 0.563250],\n    [0.140603, 0.021687, 0.566959],\n    [0.148607, 0.021154, 0.570562],\n    [0.156421, 0.020651, 0.574065],\n    [0.164070, 0.020171, 0.577478],\n    [0.171574, 0.019706, 0.580806],\n    [0.178950, 0.019252, 0.584054],\n    [0.186213, 0.018803, 0.587228],\n    [0.193374, 0.018354, 0.590330],\n    [0.200445, 0.017902, 0.593364],\n    [0.207435, 0.017442, 0.596333],\n    [0.214350, 0.016973, 0.599239],\n    [0.221197, 0.016497, 0.602083],\n    [0.227983, 0.016007, 0.604867],\n    [0.234715, 0.015502, 0.607592],\n    [0.241396, 0.014979, 0.610259],\n    [0.248032, 0.014439, 0.612868],\n    [0.254627, 0.013882, 0.615419],\n    [0.261183, 0.013308, 0.617911],\n    [0.267703, 0.012716, 0.620346],\n    [0.274191, 0.012109, 0.622722],\n    [0.280648, 0.011488, 0.625038],\n    [0.287076, 0.010855, 0.627295],\n    [0.293478, 0.010213, 0.629490],\n    [0.299855, 0.009561, 0.631624],\n    [0.306210, 0.008902, 0.633694],\n    [0.312543, 0.008239, 0.635700],\n    [0.318856, 0.007576, 0.637640],\n    [0.325150, 0.006915, 0.639512],\n    [0.331426, 0.006261, 0.641316],\n    [0.337683, 0.005618, 0.643049],\n    [0.343925, 0.004991, 0.644710],\n    [0.350150, 0.004382, 0.646298],\n    [0.356359, 0.003798, 0.647810],\n    [0.362553, 0.003243, 0.649245],\n    [0.368733, 0.002724, 0.650601],\n    [0.374897, 0.002245, 0.651876],\n    [0.381047, 0.001814, 0.653068],\n    [0.387183, 0.001434, 0.654177],\n    [0.393304, 0.001114, 0.655199],\n    [0.399411, 0.000859, 0.656133],\n    [0.405503, 0.000678, 0.656977],\n    [0.411580, 0.000577, 0.657730],\n    [0.417642, 0.000564, 0.658390],\n    [0.423689, 0.000646, 0.658956],\n    [0.429719, 0.000831, 0.659425],\n    [0.435734, 0.001127, 0.659797],\n    [0.441732, 0.001540, 0.660069],\n    [0.447714, 0.002080, 0.660240],\n    [0.453677, 0.002755, 0.660310],\n    [0.459623, 0.003574, 0.660277],\n    [0.465550, 0.004545, 0.660139],\n    [0.471457, 0.005678, 0.659897],\n    [0.477344, 0.006980, 0.659549],\n    [0.483210, 0.008460, 0.659095],\n    [0.489055, 0.010127, 0.658534],\n    [0.494877, 0.011990, 0.657865],\n    [0.500678, 0.014055, 0.657088],\n    [0.506454, 0.016333, 0.656202],\n    [0.512206, 0.018833, 0.655209],\n    [0.517933, 0.021563, 0.654109],\n    [0.523633, 0.024532, 0.652901],\n    [0.529306, 0.027747, 0.651586],\n    [0.534952, 0.031217, 0.650165],\n    [0.540570, 0.034950, 0.648640],\n    [0.546157, 0.038954, 0.647010],\n    [0.551715, 0.043136, 0.645277],\n    [0.557243, 0.047331, 0.643443],\n    [0.562738, 0.051545, 0.641509],\n    [0.568201, 0.055778, 0.639477],\n    [0.573632, 0.060028, 0.637349],\n    [0.579029, 0.064296, 0.635126],\n    [0.584391, 0.068579, 0.632812],\n    [0.589719, 0.072878, 0.630408],\n    [0.595011, 0.077190, 0.627917],\n    [0.600266, 0.081516, 0.625342],\n    [0.605485, 0.085854, 0.622686],\n    [0.610667, 0.090204, 0.619951],\n    [0.615812, 0.094564, 0.617140],\n    [0.620919, 0.098934, 0.614257],\n    [0.625987, 0.103312, 0.611305],\n    [0.631017, 0.107699, 0.608287],\n    [0.636008, 0.112092, 0.605205],\n    [0.640959, 0.116492, 0.602065],\n    [0.645872, 0.120898, 0.598867],\n    [0.650746, 0.125309, 0.595617],\n    [0.655580, 0.129725, 0.592317],\n    [0.660374, 0.134144, 0.588971],\n    [0.665129, 0.138566, 0.585582],\n    [0.669845, 0.142992, 0.582154],\n    [0.674522, 0.147419, 0.578688],\n    [0.679160, 0.151848, 0.575189],\n    [0.683758, 0.156278, 0.571660],\n    [0.688318, 0.160709, 0.568103],\n    [0.692840, 0.165141, 0.564522],\n    [0.697324, 0.169573, 0.560919],\n    [0.701769, 0.174005, 0.557296],\n    [0.706178, 0.178437, 0.553657],\n    [0.710549, 0.182868, 0.550004],\n    [0.714883, 0.187299, 0.546338],\n    [0.719181, 0.191729, 0.542663],\n    [0.723444, 0.196158, 0.538981],\n    [0.727670, 0.200586, 0.535293],\n    [0.731862, 0.205013, 0.531601],\n    [0.736019, 0.209439, 0.527908],\n    [0.740143, 0.213864, 0.524216],\n    [0.744232, 0.218288, 0.520524],\n    [0.748289, 0.222711, 0.516834],\n    [0.752312, 0.227133, 0.513149],\n    [0.756304, 0.231555, 0.509468],\n    [0.760264, 0.235976, 0.505794],\n    [0.764193, 0.240396, 0.502126],\n    [0.768090, 0.244817, 0.498465],\n    [0.771958, 0.249237, 0.494813],\n    [0.775796, 0.253658, 0.491171],\n    [0.779604, 0.258078, 0.487539],\n    [0.783383, 0.262500, 0.483918],\n    [0.787133, 0.266922, 0.480307],\n    [0.790855, 0.271345, 0.476706],\n    [0.794549, 0.275770, 0.473117],\n    [0.798216, 0.280197, 0.469538],\n    [0.801855, 0.284626, 0.465971],\n    [0.805467, 0.289057, 0.462415],\n    [0.809052, 0.293491, 0.458870],\n    [0.812612, 0.297928, 0.455338],\n    [0.816144, 0.302368, 0.451816],\n    [0.819651, 0.306812, 0.448306],\n    [0.823132, 0.311261, 0.444806],\n    [0.826588, 0.315714, 0.441316],\n    [0.830018, 0.320172, 0.437836],\n    [0.833422, 0.324635, 0.434366],\n    [0.836801, 0.329105, 0.430905],\n    [0.840155, 0.333580, 0.427455],\n    [0.843484, 0.338062, 0.424013],\n    [0.846788, 0.342551, 0.420579],\n    [0.850066, 0.347048, 0.417153],\n    [0.853319, 0.351553, 0.413734],\n    [0.856547, 0.356066, 0.410322],\n    [0.859750, 0.360588, 0.406917],\n    [0.862927, 0.365119, 0.403519],\n    [0.866078, 0.369660, 0.400126],\n    [0.869203, 0.374212, 0.396738],\n    [0.872303, 0.378774, 0.393355],\n    [0.875376, 0.383347, 0.389976],\n    [0.878423, 0.387932, 0.386600],\n    [0.881443, 0.392529, 0.383229],\n    [0.884436, 0.397139, 0.379860],\n    [0.887402, 0.401762, 0.376494],\n    [0.890340, 0.406398, 0.373130],\n    [0.893250, 0.411048, 0.369768],\n    [0.896131, 0.415712, 0.366407],\n    [0.898984, 0.420392, 0.363047],\n    [0.901807, 0.425087, 0.359688],\n    [0.904601, 0.429797, 0.356329],\n    [0.907365, 0.434524, 0.352970],\n    [0.910098, 0.439268, 0.349610],\n    [0.912800, 0.444029, 0.346251],\n    [0.915471, 0.448807, 0.342890],\n    [0.918109, 0.453603, 0.339529],\n    [0.920714, 0.458417, 0.336166],\n    [0.923287, 0.463251, 0.332801],\n    [0.925825, 0.468103, 0.329435],\n    [0.928329, 0.472975, 0.326067],\n    [0.930798, 0.477867, 0.322697],\n    [0.933232, 0.482780, 0.319325],\n    [0.935630, 0.487712, 0.315952],\n    [0.937990, 0.492667, 0.312575],\n    [0.940313, 0.497642, 0.309197],\n    [0.942598, 0.502639, 0.305816],\n    [0.944844, 0.507658, 0.302433],\n    [0.947051, 0.512699, 0.299049],\n    [0.949217, 0.517763, 0.295662],\n    [0.951344, 0.522850, 0.292275],\n    [0.953428, 0.527960, 0.288883],\n    [0.955470, 0.533093, 0.285490],\n    [0.957469, 0.538250, 0.282096],\n    [0.959424, 0.543431, 0.278701],\n    [0.961336, 0.548636, 0.275305],\n    [0.963203, 0.553865, 0.271909],\n    [0.965024, 0.559118, 0.268513],\n    [0.966798, 0.564396, 0.265118],\n    [0.968526, 0.569700, 0.261721],\n    [0.970205, 0.575028, 0.258325],\n    [0.971835, 0.580382, 0.254931],\n    [0.973416, 0.585761, 0.251540],\n    [0.974947, 0.591165, 0.248151],\n    [0.976428, 0.596595, 0.244767],\n    [0.977856, 0.602051, 0.241387],\n    [0.979233, 0.607532, 0.238013],\n    [0.980556, 0.613039, 0.234646],\n    [0.981826, 0.618572, 0.231287],\n    [0.983041, 0.624131, 0.227937],\n    [0.984199, 0.629718, 0.224595],\n    [0.985301, 0.635330, 0.221265],\n    [0.986345, 0.640969, 0.217948],\n    [0.987332, 0.646633, 0.214648],\n    [0.988260, 0.652325, 0.211364],\n    [0.989128, 0.658043, 0.208100],\n    [0.989935, 0.663787, 0.204859],\n    [0.990681, 0.669558, 0.201642],\n    [0.991365, 0.675355, 0.198453],\n    [0.991985, 0.681179, 0.195295],\n    [0.992541, 0.687030, 0.192170],\n    [0.993032, 0.692907, 0.189084],\n    [0.993456, 0.698810, 0.186041],\n    [0.993814, 0.704741, 0.183043],\n    [0.994103, 0.710698, 0.180097],\n    [0.994324, 0.716681, 0.177208],\n    [0.994474, 0.722691, 0.174381],\n    [0.994553, 0.728728, 0.171622],\n    [0.994561, 0.734791, 0.168938],\n    [0.994495, 0.740880, 0.166335],\n    [0.994355, 0.746995, 0.163821],\n    [0.994141, 0.753137, 0.161404],\n    [0.993851, 0.759304, 0.159092],\n    [0.993482, 0.765499, 0.156891],\n    [0.993033, 0.771720, 0.154808],\n    [0.992505, 0.777967, 0.152855],\n    [0.991897, 0.784239, 0.151042],\n    [0.991209, 0.790537, 0.149377],\n    [0.990439, 0.796859, 0.147870],\n    [0.989587, 0.803205, 0.146529],\n    [0.988648, 0.809579, 0.145357],\n    [0.987621, 0.815978, 0.144363],\n    [0.986509, 0.822401, 0.143557],\n    [0.985314, 0.828846, 0.142945],\n    [0.984031, 0.835315, 0.142528],\n    [0.982653, 0.841812, 0.142303],\n    [0.981190, 0.848329, 0.142279],\n    [0.979644, 0.854866, 0.142453],\n    [0.977995, 0.861432, 0.142808],\n    [0.976265, 0.868016, 0.143351],\n    [0.974443, 0.874622, 0.144061],\n    [0.972530, 0.881250, 0.144923],\n    [0.970533, 0.887896, 0.145919],\n    [0.968443, 0.894564, 0.147014],\n    [0.966271, 0.901249, 0.148180],\n    [0.964021, 0.907950, 0.149370],\n    [0.961681, 0.914672, 0.150520],\n    [0.959276, 0.921407, 0.151566],\n    [0.956808, 0.928152, 0.152409],\n    [0.954287, 0.934908, 0.152921],\n    [0.951726, 0.941671, 0.152925],\n    [0.949151, 0.948435, 0.152178],\n    [0.946602, 0.955190, 0.150328],\n    [0.944152, 0.961916, 0.146861],\n    [0.941896, 0.968590, 0.140956],\n    [0.940015, 0.975158, 0.131326]\n  ];\n  const INFERNO = [\n    [0.001462, 0.000466, 0.013866],\n    [0.002267, 0.001270, 0.018570],\n    [0.003299, 0.002249, 0.024239],\n    [0.004547, 0.003392, 0.030909],\n    [0.006006, 0.004692, 0.038558],\n    [0.007676, 0.006136, 0.046836],\n    [0.009561, 0.007713, 0.055143],\n    [0.011663, 0.009417, 0.063460],\n    [0.013995, 0.011225, 0.071862],\n    [0.016561, 0.013136, 0.080282],\n    [0.019373, 0.015133, 0.088767],\n    [0.022447, 0.017199, 0.097327],\n    [0.025793, 0.019331, 0.105930],\n    [0.029432, 0.021503, 0.114621],\n    [0.033385, 0.023702, 0.123397],\n    [0.037668, 0.025921, 0.132232],\n    [0.042253, 0.028139, 0.141141],\n    [0.046915, 0.030324, 0.150164],\n    [0.051644, 0.032474, 0.159254],\n    [0.056449, 0.034569, 0.168414],\n    [0.061340, 0.036590, 0.177642],\n    [0.066331, 0.038504, 0.186962],\n    [0.071429, 0.040294, 0.196354],\n    [0.076637, 0.041905, 0.205799],\n    [0.081962, 0.043328, 0.215289],\n    [0.087411, 0.044556, 0.224813],\n    [0.092990, 0.045583, 0.234358],\n    [0.098702, 0.046402, 0.243904],\n    [0.104551, 0.047008, 0.253430],\n    [0.110536, 0.047399, 0.262912],\n    [0.116656, 0.047574, 0.272321],\n    [0.122908, 0.047536, 0.281624],\n    [0.129285, 0.047293, 0.290788],\n    [0.135778, 0.046856, 0.299776],\n    [0.142378, 0.046242, 0.308553],\n    [0.149073, 0.045468, 0.317085],\n    [0.155850, 0.044559, 0.325338],\n    [0.162689, 0.043554, 0.333277],\n    [0.169575, 0.042489, 0.340874],\n    [0.176493, 0.041402, 0.348111],\n    [0.183429, 0.040329, 0.354971],\n    [0.190367, 0.039309, 0.361447],\n    [0.197297, 0.038400, 0.367535],\n    [0.204209, 0.037632, 0.373238],\n    [0.211095, 0.037030, 0.378563],\n    [0.217949, 0.036615, 0.383522],\n    [0.224763, 0.036405, 0.388129],\n    [0.231538, 0.036405, 0.392400],\n    [0.238273, 0.036621, 0.396353],\n    [0.244967, 0.037055, 0.400007],\n    [0.251620, 0.037705, 0.403378],\n    [0.258234, 0.038571, 0.406485],\n    [0.264810, 0.039647, 0.409345],\n    [0.271347, 0.040922, 0.411976],\n    [0.277850, 0.042353, 0.414392],\n    [0.284321, 0.043933, 0.416608],\n    [0.290763, 0.045644, 0.418637],\n    [0.297178, 0.047470, 0.420491],\n    [0.303568, 0.049396, 0.422182],\n    [0.309935, 0.051407, 0.423721],\n    [0.316282, 0.053490, 0.425116],\n    [0.322610, 0.055634, 0.426377],\n    [0.328921, 0.057827, 0.427511],\n    [0.335217, 0.060060, 0.428524],\n    [0.341500, 0.062325, 0.429425],\n    [0.347771, 0.064616, 0.430217],\n    [0.354032, 0.066925, 0.430906],\n    [0.360284, 0.069247, 0.431497],\n    [0.366529, 0.071579, 0.431994],\n    [0.372768, 0.073915, 0.432400],\n    [0.379001, 0.076253, 0.432719],\n    [0.385228, 0.078591, 0.432955],\n    [0.391453, 0.080927, 0.433109],\n    [0.397674, 0.083257, 0.433183],\n    [0.403894, 0.085580, 0.433179],\n    [0.410113, 0.087896, 0.433098],\n    [0.416331, 0.090203, 0.432943],\n    [0.422549, 0.092501, 0.432714],\n    [0.428768, 0.094790, 0.432412],\n    [0.434987, 0.097069, 0.432039],\n    [0.441207, 0.099338, 0.431594],\n    [0.447428, 0.101597, 0.431080],\n    [0.453651, 0.103848, 0.430498],\n    [0.459875, 0.106089, 0.429846],\n    [0.466100, 0.108322, 0.429125],\n    [0.472328, 0.110547, 0.428334],\n    [0.478558, 0.112764, 0.427475],\n    [0.484789, 0.114974, 0.426548],\n    [0.491022, 0.117179, 0.425552],\n    [0.497257, 0.119379, 0.424488],\n    [0.503493, 0.121575, 0.423356],\n    [0.509730, 0.123769, 0.422156],\n    [0.515967, 0.125960, 0.420887],\n    [0.522206, 0.128150, 0.419549],\n    [0.528444, 0.130341, 0.418142],\n    [0.534683, 0.132534, 0.416667],\n    [0.540920, 0.134729, 0.415123],\n    [0.547157, 0.136929, 0.413511],\n    [0.553392, 0.139134, 0.411829],\n    [0.559624, 0.141346, 0.410078],\n    [0.565854, 0.143567, 0.408258],\n    [0.572081, 0.145797, 0.406369],\n    [0.578304, 0.148039, 0.404411],\n    [0.584521, 0.150294, 0.402385],\n    [0.590734, 0.152563, 0.400290],\n    [0.596940, 0.154848, 0.398125],\n    [0.603139, 0.157151, 0.395891],\n    [0.609330, 0.159474, 0.393589],\n    [0.615513, 0.161817, 0.391219],\n    [0.621685, 0.164184, 0.388781],\n    [0.627847, 0.166575, 0.386276],\n    [0.633998, 0.168992, 0.383704],\n    [0.640135, 0.171438, 0.381065],\n    [0.646260, 0.173914, 0.378359],\n    [0.652369, 0.176421, 0.375586],\n    [0.658463, 0.178962, 0.372748],\n    [0.664540, 0.181539, 0.369846],\n    [0.670599, 0.184153, 0.366879],\n    [0.676638, 0.186807, 0.363849],\n    [0.682656, 0.189501, 0.360757],\n    [0.688653, 0.192239, 0.357603],\n    [0.694627, 0.195021, 0.354388],\n    [0.700576, 0.197851, 0.351113],\n    [0.706500, 0.200728, 0.347777],\n    [0.712396, 0.203656, 0.344383],\n    [0.718264, 0.206636, 0.340931],\n    [0.724103, 0.209670, 0.337424],\n    [0.729909, 0.212759, 0.333861],\n    [0.735683, 0.215906, 0.330245],\n    [0.741423, 0.219112, 0.326576],\n    [0.747127, 0.222378, 0.322856],\n    [0.752794, 0.225706, 0.319085],\n    [0.758422, 0.229097, 0.315266],\n    [0.764010, 0.232554, 0.311399],\n    [0.769556, 0.236077, 0.307485],\n    [0.775059, 0.239667, 0.303526],\n    [0.780517, 0.243327, 0.299523],\n    [0.785929, 0.247056, 0.295477],\n    [0.791293, 0.250856, 0.291390],\n    [0.796607, 0.254728, 0.287264],\n    [0.801871, 0.258674, 0.283099],\n    [0.807082, 0.262692, 0.278898],\n    [0.812239, 0.266786, 0.274661],\n    [0.817341, 0.270954, 0.270390],\n    [0.822386, 0.275197, 0.266085],\n    [0.827372, 0.279517, 0.261750],\n    [0.832299, 0.283913, 0.257383],\n    [0.837165, 0.288385, 0.252988],\n    [0.841969, 0.292933, 0.248564],\n    [0.846709, 0.297559, 0.244113],\n    [0.851384, 0.302260, 0.239636],\n    [0.855992, 0.307038, 0.235133],\n    [0.860533, 0.311892, 0.230606],\n    [0.865006, 0.316822, 0.226055],\n    [0.869409, 0.321827, 0.221482],\n    [0.873741, 0.326906, 0.216886],\n    [0.878001, 0.332060, 0.212268],\n    [0.882188, 0.337287, 0.207628],\n    [0.886302, 0.342586, 0.202968],\n    [0.890341, 0.347957, 0.198286],\n    [0.894305, 0.353399, 0.193584],\n    [0.898192, 0.358911, 0.188860],\n    [0.902003, 0.364492, 0.184116],\n    [0.905735, 0.370140, 0.179350],\n    [0.909390, 0.375856, 0.174563],\n    [0.912966, 0.381636, 0.169755],\n    [0.916462, 0.387481, 0.164924],\n    [0.919879, 0.393389, 0.160070],\n    [0.923215, 0.399359, 0.155193],\n    [0.926470, 0.405389, 0.150292],\n    [0.929644, 0.411479, 0.145367],\n    [0.932737, 0.417627, 0.140417],\n    [0.935747, 0.423831, 0.135440],\n    [0.938675, 0.430091, 0.130438],\n    [0.941521, 0.436405, 0.125409],\n    [0.944285, 0.442772, 0.120354],\n    [0.946965, 0.449191, 0.115272],\n    [0.949562, 0.455660, 0.110164],\n    [0.952075, 0.462178, 0.105031],\n    [0.954506, 0.468744, 0.099874],\n    [0.956852, 0.475356, 0.094695],\n    [0.959114, 0.482014, 0.089499],\n    [0.961293, 0.488716, 0.084289],\n    [0.963387, 0.495462, 0.079073],\n    [0.965397, 0.502249, 0.073859],\n    [0.967322, 0.509078, 0.068659],\n    [0.969163, 0.515946, 0.063488],\n    [0.970919, 0.522853, 0.058367],\n    [0.972590, 0.529798, 0.053324],\n    [0.974176, 0.536780, 0.048392],\n    [0.975677, 0.543798, 0.043618],\n    [0.977092, 0.550850, 0.039050],\n    [0.978422, 0.557937, 0.034931],\n    [0.979666, 0.565057, 0.031409],\n    [0.980824, 0.572209, 0.028508],\n    [0.981895, 0.579392, 0.026250],\n    [0.982881, 0.586606, 0.024661],\n    [0.983779, 0.593849, 0.023770],\n    [0.984591, 0.601122, 0.023606],\n    [0.985315, 0.608422, 0.024202],\n    [0.985952, 0.615750, 0.025592],\n    [0.986502, 0.623105, 0.027814],\n    [0.986964, 0.630485, 0.030908],\n    [0.987337, 0.637890, 0.034916],\n    [0.987622, 0.645320, 0.039886],\n    [0.987819, 0.652773, 0.045581],\n    [0.987926, 0.660250, 0.051750],\n    [0.987945, 0.667748, 0.058329],\n    [0.987874, 0.675267, 0.065257],\n    [0.987714, 0.682807, 0.072489],\n    [0.987464, 0.690366, 0.079990],\n    [0.987124, 0.697944, 0.087731],\n    [0.986694, 0.705540, 0.095694],\n    [0.986175, 0.713153, 0.103863],\n    [0.985566, 0.720782, 0.112229],\n    [0.984865, 0.728427, 0.120785],\n    [0.984075, 0.736087, 0.129527],\n    [0.983196, 0.743758, 0.138453],\n    [0.982228, 0.751442, 0.147565],\n    [0.981173, 0.759135, 0.156863],\n    [0.980032, 0.766837, 0.166353],\n    [0.978806, 0.774545, 0.176037],\n    [0.977497, 0.782258, 0.185923],\n    [0.976108, 0.789974, 0.196018],\n    [0.974638, 0.797692, 0.206332],\n    [0.973088, 0.805409, 0.216877],\n    [0.971468, 0.813122, 0.227658],\n    [0.969783, 0.820825, 0.238686],\n    [0.968041, 0.828515, 0.249972],\n    [0.966243, 0.836191, 0.261534],\n    [0.964394, 0.843848, 0.273391],\n    [0.962517, 0.851476, 0.285546],\n    [0.960626, 0.859069, 0.298010],\n    [0.958720, 0.866624, 0.310820],\n    [0.956834, 0.874129, 0.323974],\n    [0.954997, 0.881569, 0.337475],\n    [0.953215, 0.888942, 0.351369],\n    [0.951546, 0.896226, 0.365627],\n    [0.950018, 0.903409, 0.380271],\n    [0.948683, 0.910473, 0.395289],\n    [0.947594, 0.917399, 0.410665],\n    [0.946809, 0.924168, 0.426373],\n    [0.946392, 0.930761, 0.442367],\n    [0.946403, 0.937159, 0.458592],\n    [0.946903, 0.943348, 0.474970],\n    [0.947937, 0.949318, 0.491426],\n    [0.949545, 0.955063, 0.507860],\n    [0.951740, 0.960587, 0.524203],\n    [0.954529, 0.965896, 0.540361],\n    [0.957896, 0.971003, 0.556275],\n    [0.961812, 0.975924, 0.571925],\n    [0.966249, 0.980678, 0.587206],\n    [0.971162, 0.985282, 0.602154],\n    [0.976511, 0.989753, 0.616760],\n    [0.982257, 0.994109, 0.631017],\n    [0.988362, 0.998364, 0.644924]\n  ];\n  const grayscaleInverted = () => {\n    const scale = [];\n    for (let i = 0; i < 256; i++) {\n      let val = 1 - (i / 256);\n      //val = Math.pow(val, 2.3);\n      scale.push([val, val, val]);\n    }\n    return scale;\n  };\n  const GRAYSCALE_INVERTED = grayscaleInverted();\n  // More maps: https://github.com/bpostlethwaite/colormap/tree/master/res/res\n  const COLOR_MAPS = {\n    plasma: PLASMA,\n    viridis: VIRIDIS,\n    grey: GRAYSCALE_INVERTED,\n    inferno: INFERNO,\n  };\n\n  // language=GLSL\n  const vertexShaderSource = `#version 300 es\nin vec4 a_position;\nin vec2 a_uv;\nuniform highp vec4 u_uv;\n\nmat4 matrix = mat4(\n2.0, 0.0, 0.0, 0.0,\n0.0, -2.0, 0.0, 0.0,\n0.0, 0.0, -1.0, 0.0,\n-1.0, 1.0, 0.0, 1.0\n);\nout vec2 v_texcoord;\n\nvoid main() {\n    gl_Position = matrix * a_position;\n    v_texcoord = a_uv * vec2(1.0, (u_uv.y - u_uv.x)) + vec2(0.0, u_uv.x);\n}\n`;\n\n  // language=GLSL\n  const fragmentShaderSource = `#version 300 es\nprecision highp float;\n\nin vec2 v_texcoord;\nuniform highp vec4 u_uv;\n\nuniform highp sampler2DArray u_colormap;\nuniform highp sampler2DArray u_texture;\n\nuniform highp float u_colormap_index;\nuniform highp float u_spectrogram_index;\nuniform highp vec3 u_crop_y;\n\n// TODO: Probably need to pass in min/max if we want to clamp to selected tracks.\nuniform highp float u_scale;\n\nout vec4 outColor;\n\nfloat map(float value, float min1, float max1, float min2, float max2) {\n    return min2 + (value - min1) * (max2 - min2) / (max1 - min1);\n}\n\n#define INV_LOG_10 0.43429448190325176\n\nvoid main() {\n    float y = v_texcoord.x;\n    float x = v_texcoord.y;\n\n    // How much to crop of the top and bottom of the spectrogram (used if the sample rate of the audio was different\n    // from the sample rate the FFT was performed at, since that leaves a blank space at the top) \n    float crop_top = u_crop_y.x;\n    float crop_bottom = u_crop_y.y;\n    float max_zoom = u_crop_y.z;// Stuff max zoom into this array too.\n    float max_y_zoom = max_zoom * 0.8;\n    float min_range_y = 1.0 / max_zoom;\n    float top = u_uv.z;\n    float bottom = u_uv.w;\n    float range_y = top - bottom;\n    float clamped_range_y = max(range_y, min_range_y);\n    // Prevent divide by zero\n    float pos_y = bottom / max(0.000001, (1.0 - range_y));\n    float m_max_zoom = map(clamped_range_y, 1.0, min_range_y, 1.0, 1.0 / max_y_zoom);\n    float actual_height = clamped_range_y * (1.0 / m_max_zoom);\n    float remainder = 1.0 - actual_height;\n    float selected_bottom = remainder * pos_y;\n    float selected_top = selected_bottom + actual_height;\n    //vec4 overlay_debug_color = vec4(0.0, 0.0, 0.0, 0.0);\n    bool above_range = y > selected_top;\n    bool below_range = y < selected_bottom;\n    bool in_range = y <= selected_top && y >= selected_bottom;\n\n    if (in_range) {\n        y = map(y, selected_bottom, selected_top, bottom, top);\n\n        // BLUE\n        //overlay_debug_color = vec4(0.0, 0.0, 1.0, 0.2);\n    } else if (below_range) {\n        y = map(y, 0.0, selected_bottom, 0.0, bottom);\n\n        // GREEN\n        //overlay_debug_color = vec4(0.0, 1.0, 0.0, .2);\n    } else if (above_range) {\n        y = map(y, selected_top, 1.0, top, 1.0);\n\n        // RED\n        //overlay_debug_color = vec4(1.0, 0.0, 0.0, 0.2);\n    }\n\n    y = map(y, 0.0, 1.0, crop_top, crop_bottom);\n    vec2 texcoord = vec2(y, x);\n    vec4 c = texture(u_texture, vec3(texcoord.x, texcoord.y, u_spectrogram_index));\n    float e = INV_LOG_10 * log(c.r);\n    float energyNormalised = e * u_scale;\n    float norm = energyNormalised * energyNormalised * energyNormalised * energyNormalised * energyNormalised * energyNormalised;\n\n    // TODO: Mel scale?\n    vec3 colorMapVal = texture(u_colormap, vec3(norm, 0.5, u_colormap_index)).rgb;\n    //outColor = vec4(mix(colorMapVal, overlay_debug_color.rgb, overlay_debug_color.a), 1.0);\n    outColor = vec4(colorMapVal, 1.0);\n}\n`;\n\n  // TODO: If there is a long time between swapping from low-res frame to the next hi-res frame, can we cross-fade a blend\n  //  between the two in our shader?\n  const texturesForContexts = new Map();\n  const drawImage$1 = (\n    gl,\n    program,\n    scale,\n    left,\n    right,\n    top,\n    bottom,\n    cropBottom,\n    cropTop,\n    spectrogramTextureIndex = 0,\n    paletteTextureIndex = 0\n  ) => {\n    const texture = texturesForContexts.get(gl);\n    // Tell WebGL how to convert from clip space to pixels\n    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);\n    // Clear the canvas\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n    const textureLocation = gl.getUniformLocation(program, \"u_texture\");\n    const scaleLocation = gl.getUniformLocation(program, \"u_scale\");\n    const colormapIndexLocation = gl.getUniformLocation(\n      program,\n      \"u_colormap_index\"\n    );\n    const spectrogramImageIndexLocation = gl.getUniformLocation(program, \"u_spectrogram_index\");\n    const colormapLocation = gl.getUniformLocation(program, \"u_colormap\");\n    const cropY = gl.getUniformLocation(program, \"u_crop_y\");\n    // We don't really have UV coords like this, they have to come through as vertex array objects.\n    const uvCoords = gl.getUniformLocation(program, \"u_uv\");\n\n    // normalisation scale log10, and log10 constant for doing log10 in shader\n    gl.uniform1f(scaleLocation, 1.0 / Math.log10(scale));\n    gl.uniform1f(colormapIndexLocation, paletteTextureIndex);\n    gl.uniform1f(spectrogramImageIndexLocation, spectrogramTextureIndex);\n    gl.uniform1i(colormapLocation, 0);\n    gl.uniform1i(textureLocation, 1);\n    cropBottom = 1.0 - cropBottom;\n    const maxYZoom = Math.max(gl.canvas.height, texture.texWidth) / (gl.canvas.height / window.devicePixelRatio);\n    gl.uniform4fv(uvCoords, new Float32Array([left, right, top, bottom]));\n    gl.uniform3fv(cropY, new Float32Array([cropTop, cropBottom, maxYZoom]));\n    // draw the quad (2 triangles, 6 vertices)\n    const offset = 0;\n    const count = 6;\n    gl.drawArrays(gl.TRIANGLES, offset, count);\n  };\n\n  const mapRange = (value, min1, max1, min2, max2) => {\n    return min2 + ((value - min1) * (max2 - min2)) / (max1 - min1);\n  };\n\n  const initMainTexture = (gl, texWidth, texHeight) => {\n    // Texture array for raw spectrogram textures:\n    const hasFloatLinear = !!gl.getExtension(\"OES_texture_float_linear\");\n    const texture = gl.createTexture();\n    texturesForContexts.set(gl, {texture, texWidth, texHeight});\n    gl.activeTexture(gl.TEXTURE1);\n    gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);\n    gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    if (hasFloatLinear) {\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    } else {\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n    }\n    const empty = new Float32Array(new ArrayBuffer(texWidth * 3 * texHeight * 4));// Dummy data;\n    gl.texImage3D(\n      gl.TEXTURE_2D_ARRAY,\n      0, // mip level\n      gl.R32F,\n      texWidth,\n      texHeight,\n      2, // Num slices, could be more in the future if we do more clever things to composite partial frames\n      0,\n      gl.RED,\n      gl.FLOAT,\n      empty\n    );\n    return {texture};\n  };\n\n  const submitTexture$1 = (gl, index, bitmap, texWidth, texHeight) => {\n    let texture;\n    if (!texturesForContexts.get(gl)) {\n      texture = initMainTexture(gl, texWidth, texHeight);\n    } else {\n      texture = texturesForContexts.get(gl);\n      if (texture.texWidth !== texWidth || texture.texHeight !== texHeight) {\n        texture = initMainTexture(gl, texWidth, texHeight);\n      }\n    }\n    gl.activeTexture(gl.TEXTURE1);\n    gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture.texture);\n    gl.texSubImage3D(\n      gl.TEXTURE_2D_ARRAY,\n      0, // mip level\n      0, // x offset\n      0, // y offset\n      index,\n      texWidth,\n      texHeight,\n      1,\n      gl.RED,\n      gl.FLOAT,\n      bitmap\n    );\n  };\n\n  const init = (gl) => {\n    const hasFloatLinear = !!gl.getExtension(\"OES_texture_float_linear\");\n    const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n    gl.shaderSource(vertexShader, vertexShaderSource);\n    gl.compileShader(vertexShader);\n\n    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n    gl.shaderSource(fragmentShader, fragmentShaderSource);\n    gl.compileShader(fragmentShader);\n\n    const program = gl.createProgram();\n    gl.attachShader(program, vertexShader);\n    gl.attachShader(program, fragmentShader);\n    gl.linkProgram(program);\n    const maps = [\n      VIRIDIS.flat(),\n      PLASMA.flat(),\n      INFERNO.flat(),\n      GRAYSCALE_INVERTED.flat(),\n    ];\n    const colorMaps = new Float32Array(maps.flat());\n    {\n      const texture = gl.createTexture();\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n      gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n      if (hasFloatLinear) {\n        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n      } else {\n        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n      }\n      gl.texImage3D(\n        gl.TEXTURE_2D_ARRAY,\n        0,\n        gl.RGB32F,\n        maps[0].length / 3,\n        1,\n        maps.length,\n        0,\n        gl.RGB,\n        gl.FLOAT,\n        colorMaps\n      );\n    }\n\n    // look up where the vertex data needs to go.\n    const positionAttributeLocation = gl.getAttribLocation(program, \"a_position\");\n    const uvAttributeLocation = gl.getAttribLocation(program, \"a_uv\");\n\n    // Create a vertex array object (attribute state)\n    const vao = gl.createVertexArray();\n\n    // and make it the one we're currently working with\n    gl.bindVertexArray(vao);\n    {\n      // create the position buffer, make it the current ARRAY_BUFFER\n      // and copy in the color values\n      const positionBuffer = gl.createBuffer();\n      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n      // Put a unit quad in the buffer\n      const positions = new Float32Array([\n        0,\n        0, // Bottom left\n        0,\n        1, // Top left\n        1,\n        0, // Bottom right\n        1,\n        0, // Bottom right\n        0,\n        1, // Top left\n        1,\n        1, // Top right\n      ]);\n      gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);\n\n      // Turn on the attribute\n      gl.enableVertexAttribArray(positionAttributeLocation);\n\n      // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)\n      const size = 2; // 2 components per iteration\n      const type = gl.FLOAT; // the data is 32bit floats\n      const normalize = false; // don't normalize the data\n      const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position\n      const offset = 0; // start at the beginning of the buffer\n      gl.vertexAttribPointer(\n        positionAttributeLocation,\n        size,\n        type,\n        normalize,\n        stride,\n        offset\n      );\n    }\n\n    {\n      const texcoordBuffer = gl.createBuffer();\n      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);\n      // Try to remove the zeroth bucket (which often contains high intensity noise)\n      const removeZero = 1.0 / 1024.0;\n      // Put texcoords in the buffer\n      const uvs = new Float32Array([\n        1,\n        0, // Bottom left\n        removeZero,\n        0, // Top left\n        1,\n        1, // Bottom right\n        1,\n        1, // Bottom right\n        removeZero,\n        0, // Top right\n        removeZero,\n        1, // Top left\n      ]);\n      gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);\n\n      // Turn on the attribute\n      gl.enableVertexAttribArray(uvAttributeLocation);\n\n      // Tell the attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)\n      const size = 2; // 2 components per iteration\n      const type = gl.FLOAT; // the data is 32bit floats\n      const normalize = true; // convert from 0-255 to 0.0-1.0\n      const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next color\n      const offset = 0; // start at the beginning of the buffer\n      gl.vertexAttribPointer(\n        uvAttributeLocation,\n        size,\n        type,\n        normalize,\n        stride,\n        offset\n      );\n      gl.useProgram(program);\n    }\n\n    return {\n      drawImage: (\n        imgIndex,\n        scale,\n        left,\n        right,\n        top,\n        bottom,\n        cropTop,\n        cropBottom,\n        colorMapIndex\n      ) =>\n        drawImage$1(\n          gl,\n          program,\n          scale,\n          left,\n          right,\n          top,\n          bottom,\n          cropTop,\n          cropBottom,\n          imgIndex,\n          colorMapIndex\n        ),\n      submitTexture: (index, bitmap, width, height) => submitTexture$1(gl, index, bitmap, width, height)\n    };\n    // Unlike images, textures do not have a width and height associated\n    // with them so we'll pass in the width and height of the texture\n  };\n\n  const getMaxXZoom = (canvasWidth, state) => {\n    const audioSamples = state.numAudioSamples;\n    if (audioSamples) {\n      // About 180 samples per pixel looks about max res at FFT size of 2048\n      return (audioSamples / canvasWidth / 180) * window.devicePixelRatio;\n    } else {\n      return 16;\n    }\n  };\n\n  const getMaxYZoom = (canvasHeight) => {\n    const FFT_WINDOW_SIZE = 2048;\n    const frequencyResolution = FFT_WINDOW_SIZE / 2;\n    // About 180 samples per pixel looks about max res at FFT size of 2048\n    //return frequencyResolution / canvasHeight; // * window.devicePixelRatio;\n\n\n    // FIXME: Should this be * 0.8?  Maybe it's being clamped elsewhere?\n    return (frequencyResolution / (canvasHeight / window.devicePixelRatio));\n\n  };\n\n  const setInitialZoom = (\n    left,\n    right,\n    top,\n    bottom,\n    state,\n    sharedState,\n    timelineElements,\n    initial,\n    final\n  ) => {\n    // Update zoom level\n    const zoomXLength = right - left;\n    const zoomXToSet = 1 / zoomXLength;\n    const zoomYLength = top - bottom;\n    const zoomYToSet = 1 / zoomYLength;\n    const maxXZoom = getMaxXZoom(timelineElements.canvas.width, state);\n    const maxYZoom = getMaxYZoom(timelineElements.canvas.height);\n    const newXZoom = Math.min(maxXZoom, zoomXToSet);\n    const newYZoom = Math.min(maxYZoom, zoomYToSet);\n    state.left = left;\n    state.right = right;\n    state.zoomX = newXZoom;\n    state.top = top;\n    state.bottom = bottom;\n    state.zoomY = newYZoom;\n\n    timelineElements.overlayCanvas.dispatchEvent(\n      new CustomEvent(\"range-change\", {\n        detail: {\n          startZeroOne: state.left,\n          endZeroOne: state.right,\n          top: state.top,\n          bottom: state.bottom,\n          initialRender: initial,\n          force: final,\n        },\n      })\n    );\n  };\n\n  const updateZoom = (\n    pXRatio,\n    zoomAmount,\n    state,\n    sharedState,\n    timelineElements\n  ) => {\n    const initialLeft = state.left;\n    const initialRight = state.right;\n    // Save prev zoom level\n    const visiblePortionI = 1 / state.zoomX;\n    const invisiblePortionI = 1 - visiblePortionI; // How much offscreen to distribute.\n    // Update zoom level\n    state.zoomX += Math.min(1, zoomAmount);\n    state.zoomX = Math.max(1, state.zoomX);\n    state.zoomX = Math.min(\n      getMaxXZoom(timelineElements.canvas.width, state),\n      state.zoomX\n    );\n    if (state.zoomX === 1) {\n      state.left = 0;\n      state.right = 1;\n    } else {\n      // See how much zoom level has changed, and how much we have to distribute.\n      const visiblePortion = 1 / state.zoomX;\n      const invisiblePortion = 1 - visiblePortion; // How much offscreen to distribute.\n      // Distribute proportionally on either side of pX the increase in width/zoom.\n      const newWToDistribute = invisiblePortion - invisiblePortionI;\n      const leftShouldTake = newWToDistribute * pXRatio;\n      const rightShouldTake = newWToDistribute * (1 - pXRatio);\n      const prevLeft = state.left;\n      const prevRight = state.right;\n\n      state.left += leftShouldTake;\n      state.left = Math.max(0, state.left);\n      // NOTE: Balance out if one side took less than it's fair share.\n      const leftTook = state.left - prevLeft;\n\n      state.right -= newWToDistribute * (1 - pXRatio);\n      state.right -= Math.min(0, leftShouldTake - leftTook);\n      state.right = Math.min(1, state.right);\n\n      // NOTE: If right didn't take everything it could, redistribute to the left.\n      const rightTook = prevRight - state.right;\n      state.left += Math.min(0, rightShouldTake - rightTook);\n    }\n\n    if (!sharedState.interacting) {\n      const changeLeft = Math.abs(initialLeft - state.left);\n      const changeRight = Math.abs(initialRight - state.right);\n      if (changeLeft !== 0 || changeRight !== 0) {\n        timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-begin\")\n        );\n        clearTimeout(sharedState.interactionTimeout);\n        sharedState.interactionTimeout = setTimeout(() => {\n          timelineElements.overlayCanvas.dispatchEvent(\n            new Event(\"interaction-end\")\n          );\n        }, 300);\n      }\n    }\n\n    timelineElements.overlayCanvas.dispatchEvent(\n      new CustomEvent(\"range-change\", {\n        detail: {\n          startZeroOne: state.left,\n          endZeroOne: state.right,\n          initialRender: false,\n        },\n      })\n    );\n  };\n\n  const updatePinch = (\n    xStartPxRatio,\n    xEndPxRatio,\n    initialXStartPxRatio,\n    initialXEndPxRatio,\n    state,\n    canvas\n  ) => {\n    // Range between two points.  This should remain constant\n    const initialRatio = initialXEndPxRatio - initialXStartPxRatio;\n    const newRatio = xEndPxRatio - xStartPxRatio;\n    const maxZoomX = getMaxXZoom(canvas.width, state);\n    const zoomX = Math.min(newRatio / initialRatio, maxZoomX);\n    const visiblePortionI = 1 / zoomX;\n    let initialLeft = initialXStartPxRatio - xStartPxRatio / zoomX;\n    let initialRight = initialLeft + visiblePortionI;\n    const range = initialXEndPxRatio - initialXStartPxRatio;\n\n    const maxOutOfBounds = range * 0.25;\n    // If left or right is outside the 0..1 bounds, start to add resistance, proportional to how much over it is.\n    if (initialLeft < 0) {\n      const a = Math.max(-maxOutOfBounds, initialLeft);\n      const b = Math.abs(a) / 2;\n      const c = Math.sqrt(b) / 3;\n      initialLeft = c * a;\n      initialRight = initialLeft + visiblePortionI;\n    } else if (initialRight > 1) {\n      if (state.panStarted) {\n        initialLeft = 1 - state.initialPanRange;\n      } else if (state.pinchStarted) {\n        initialLeft = initialRight - visiblePortionI;\n      }\n    }\n    state.left = Math.max(0, initialLeft);\n    state.right = Math.min(1, initialRight);\n    canvas.dispatchEvent(\n      new CustomEvent(\"range-change\", {\n        detail: {\n          startZeroOne: state.left,\n          endZeroOne: state.right,\n          initialRender: false,\n        },\n      })\n    );\n  };\n\n  const onPointerMove = (canvas, state, sharedState) => {\n    const numPointers = Object.keys(state.pointers).length;\n    if (numPointers <= 2) {\n      const pointers = Object.values(state.pointers);\n      const canvasWidth = canvas.width / devicePixelRatio;\n\n      let pinchXLeftZeroOne;\n      let pinchXRightZeroOne;\n      if (numPointers === 2) {\n        if (!state.panStarted || state.pinchStarted) {\n          const x0 = pointers[0].x;\n          const x1 = pointers[1].x;\n          pinchXLeftZeroOne = Math.max(0, Math.min(x0, x1) / canvasWidth);\n          pinchXRightZeroOne = Math.min(1, Math.max(x0, x1) / canvasWidth);\n          if (!state.pinchStarted) {\n            state.pinchStarted = true;\n            if (!sharedState.interacting) {\n              canvas.dispatchEvent(new Event(\"interaction-begin\"));\n            }\n            const range = state.right - state.left;\n            state.initialPinchXLeftZeroOne =\n              state.left + range * pinchXLeftZeroOne;\n            state.initialPinchXRightZeroOne =\n              state.left + range * pinchXRightZeroOne;\n          }\n        } else {\n          state.pinchStarted = false;\n          state.panStarted = false;\n        }\n      } else if (numPointers === 1) {\n        const scrubLocal = state.scrubLocalStarted;\n        const scrubGlobal = state.scrubGlobalStarted;\n        const isScrubbing = scrubGlobal || scrubLocal;\n        if (!state.pinchStarted) {\n          let range = state.right - state.left;\n          if (state.panStarted) {\n            range = state.initialPanRange;\n          }\n          const x0 = pointers[0].x / canvasWidth;\n          pinchXLeftZeroOne = Math.max(0, x0);\n          pinchXRightZeroOne = pinchXLeftZeroOne + range;\n          if (\n            !state.pinchStarted &&\n            !state.panStarted &&\n            !state.scrubGlobalStarted &&\n            !state.scrubLocalStarted\n          ) {\n            state.panStarted = true;\n            if (!sharedState.interacting) {\n              canvas.dispatchEvent(new Event(\"interaction-begin\"));\n            }\n            state.initialPinchXLeftZeroOne = Math.max(\n              0,\n              state.left + range * pinchXLeftZeroOne\n            );\n            state.initialPinchXRightZeroOne = Math.min(\n              1,\n              state.left + range * pinchXRightZeroOne\n            );\n            state.startPanXZeroOne = state.left;\n            state.initialPanRange = state.right - state.left;\n          } else if (isScrubbing) {\n            if (!sharedState.interacting) {\n              canvas.dispatchEvent(new Event(\"interaction-begin\"));\n            }\n            const progress =\n              (pointers[0].x - state.scrubDragOffsetX) / canvasWidth;\n            if (scrubLocal) {\n              state.dragLocalPlayhead(progress);\n            } else if (scrubGlobal && sharedState.displayTimeline) {\n              state.dragGlobalPlayhead(progress);\n            }\n          } else if (state.panStarted) {\n            const localLeft = Math.max(\n              0,\n              state.startPanXZeroOne + range * pinchXLeftZeroOne\n            );\n            state.left = Math.min(\n              1 - state.initialPanRange,\n              Math.max(\n                0,\n                state.startPanXZeroOne -\n                (localLeft - state.initialPinchXLeftZeroOne)\n              )\n            );\n            state.right = state.left + state.initialPanRange;\n            canvas.dispatchEvent(\n              new CustomEvent(\"range-change\", {\n                detail: {\n                  startZeroOne: state.left,\n                  endZeroOne: state.right,\n                  initialRender: false,\n                },\n              })\n            );\n          }\n        } else {\n          state.pinchStarted = false;\n          state.panStarted = false;\n        }\n      }\n      if (\n        (sharedState.interacting && state.pinchStarted && numPointers < 2) ||\n        (state.panStarted && numPointers < 1)\n      ) {\n        canvas.dispatchEvent(new Event(\"interaction-end\"));\n      }\n      // TODO: We may want to allow pinching in to less than 100%, and then bounce back out.\n      if (state.pinchStarted) {\n        updatePinch(\n          pinchXLeftZeroOne,\n          pinchXRightZeroOne,\n          state.initialPinchXLeftZeroOne,\n          state.initialPinchXRightZeroOne,\n          state,\n          canvas\n        );\n      }\n    }\n  };\n\n  const startHandleDrag = (e, timelineElements, timelineState, xOffset) => {\n    if (e.isPrimary && !timelineState.currentAction) {\n      const handle = timelineElements.timelineUICanvas;\n      handle.setPointerCapture(e.pointerId);\n      timelineState.currentAction = \"pan\";\n      if (e.pressure > 0 && e.pointerType !== \"touch\") {\n        if (!handle.classList.contains(\"grabbing\")) {\n          handle.classList.add(\"grabbing\");\n        }\n      }\n      timelineState.handleStartOffsetXZeroOne = timelineState.left;\n      timelineState.handleDragOffsetX = xOffset;\n      timelineElements.overlayCanvas.dispatchEvent(\n        new Event(\"interaction-begin\")\n      );\n    }\n  };\n\n  const endHandleDrag = (e, timelineElements, state) => {\n    if (e.isPrimary) {\n      timelineElements.timelineUICanvas.releasePointerCapture(e.pointerId);\n      if (e.pointerType !== \"touch\") {\n        if (timelineElements.timelineUICanvas.classList.contains(\"grabbing\")) {\n          timelineElements.timelineUICanvas.classList.remove(\"grabbing\", \"grab\");\n        }\n      }\n      state.currentAction = null;\n      timelineElements.overlayCanvas.dispatchEvent(new Event(\"interaction-end\"));\n    }\n  };\n\n  const dragHandle = (e, timelineElements, state, thisOffsetX) => {\n    if (\n      e.isPrimary &&\n      e.target.hasPointerCapture(e.pointerId) &&\n      state.currentAction === \"pan\"\n    ) {\n      const xOffset = state.handleDragOffsetX - state.handleStartOffsetXZeroOne;\n      const range = state.right - state.left;\n      let initialLeft = state.left;\n      let initialRight = state.right;\n      state.left = thisOffsetX - xOffset;\n      state.right = state.left + range;\n      if (state.left < 0 || state.right > 1) {\n        if (state.right > 1 && state.left > 0) {\n          state.right = 1;\n          state.left = state.right - range;\n        } else if (state.left < 0 && state.right < 1) {\n          state.left = 0;\n          state.right = state.left + range;\n        } else {\n          state.left = initialLeft;\n          state.right = initialRight;\n        }\n      }\n      timelineElements.overlayCanvas.dispatchEvent(\n        new CustomEvent(\"range-change\", {\n          detail: {\n            startZeroOne: state.left,\n            endZeroOne: state.right,\n            initialRender: false,\n          },\n        })\n      );\n    } else {\n      e.preventDefault();\n    }\n  };\n\n  const drawTimelineUI =\n    (timelineElements, state) => (startZeroOne, endZeroOne, currentAction) => {\n      // Draw handles on timelineUICanvas.\n      const isDarkTheme = state.isDarkTheme;\n      const ctx = timelineElements.timelineUICanvas.getContext(\"2d\");\n      const width = ctx.canvas.width;\n      const height = ctx.canvas.height;\n      const start = startZeroOne * width;\n      const end = endZeroOne * width;\n      const onePx = devicePixelRatio;\n      const halfPx = 0.5 * devicePixelRatio;\n      const twoPx = 2 * devicePixelRatio;\n      const threePx = 3 * devicePixelRatio;\n      const handleWidth = end - start - onePx;\n      const minHandleWidth = 44 * devicePixelRatio;\n      const handleIsNarrow = handleWidth < minHandleWidth;\n      const unselectedHandleColor = isDarkTheme ? \"#b1b2b5\" : \"#595959\";\n      const selectedHandleColor = isDarkTheme ? \"#cccdd1\" : \"#333333\";\n      const resizeHandleAt = (x, color, cX) => {\n        const y = height / 2 - handleHeight / 2;\n        const halfWidth = resizeHandleWidth / 2;\n        ctx.fillStyle = color;\n        ctx.beginPath();\n        const atStart = x < halfWidth;\n\n        const atEnd = x + resizeHandleWidth > width - halfWidth;\n        if (atStart || atEnd) {\n          if (atStart) {\n            ctx.roundRect(x, y, resizeHandleWidth, handleHeight, [\n              cX,\n              halfWidth,\n              halfWidth,\n              cX,\n            ]);\n          } else {\n            ctx.roundRect(x, y, resizeHandleWidth, handleHeight, [\n              halfWidth,\n              cX,\n              cX,\n              halfWidth,\n            ]);\n          }\n          ctx.roundRect(x, y, resizeHandleWidth, handleHeight, halfWidth);\n        } else {\n          ctx.roundRect(x, y, resizeHandleWidth, handleHeight, halfWidth);\n        }\n        ctx.fill();\n        ctx.fillStyle = isDarkTheme ? \"#666\" : \"#ccc\";\n        ctx.beginPath();\n        const circleX = x + halfWidth - 1.5 * devicePixelRatio;\n        const circleC = 3 * devicePixelRatio;\n        ctx.roundRect(\n          circleX,\n          y + 4.5 * devicePixelRatio,\n          circleC,\n          circleC,\n          circleC * 0.5\n        );\n        ctx.roundRect(\n          circleX,\n          y + 10.5 * devicePixelRatio,\n          circleC,\n          circleC,\n          circleC * 0.5\n        );\n        ctx.roundRect(\n          circleX,\n          y + 16.5 * devicePixelRatio,\n          circleC,\n          circleC,\n          circleC * 0.5\n        );\n        ctx.fill();\n      };\n\n      ctx.clearRect(0, 0, width, height);\n      ctx.fillStyle =\n        currentAction !== null ? `rgba(0, 0, 0, 0.25)` : \"rgba(0, 0, 0, 0.2)\";\n      ctx.beginPath();\n      ctx.rect(0, 0, width, height);\n\n      const vStep1 = height / 2;\n      const vStep2 = (height / 4) * 3;\n\n      const drawPanHandlePath = (handleIsNarrow, selected) => {\n        if (!handleIsNarrow) {\n          if (!selected) {\n            ctx.roundRect(\n              Math.max(halfPx, start),\n              halfPx,\n              handleWidth,\n              height - onePx,\n              threePx\n            );\n          } else {\n            ctx.roundRect(\n              start + onePx,\n              onePx,\n              handleWidth - onePx,\n              height - twoPx,\n              threePx\n            );\n          }\n        } else {\n          // Something fancy, a kind of funnel from the wide grabby bit of the handle to the width that shows how wide\n          // the current selection range actually is.\n          const left = Math.max(halfPx, start);\n          const leftWideExtreme = Math.min(\n            width - minHandleWidth - halfPx,\n            Math.max(halfPx, left - minHandleWidth / 2 + handleWidth / 2)\n          );\n          const rightWideExtreme = leftWideExtreme + minHandleWidth; //(minHandleWidth / 2) - (handleWidth / 2);\n          const top = halfPx;\n          const bottom = height - onePx;\n\n          ctx.moveTo(left, top);\n          ctx.lineTo(left + handleWidth - threePx, halfPx);\n          ctx.arcTo(\n            left + handleWidth,\n            halfPx,\n            left + handleWidth,\n            halfPx + threePx,\n            threePx\n          );\n          ctx.bezierCurveTo(\n            left + handleWidth,\n            vStep2,\n            rightWideExtreme,\n            vStep1,\n            rightWideExtreme,\n            bottom - threePx\n          );\n          ctx.arcTo(\n            rightWideExtreme,\n            bottom,\n            rightWideExtreme - threePx,\n            bottom,\n            threePx\n          );\n          ctx.lineTo(leftWideExtreme + threePx, bottom);\n          ctx.arcTo(\n            leftWideExtreme,\n            bottom,\n            leftWideExtreme,\n            bottom - threePx,\n            threePx\n          );\n          ctx.bezierCurveTo(\n            leftWideExtreme,\n            vStep1,\n            left,\n            vStep2,\n            left,\n            top + threePx\n          );\n          ctx.arcTo(left, top, left + threePx, top, threePx);\n        }\n      };\n\n      // Draw track and pan handle cutout\n      drawPanHandlePath(handleIsNarrow, false);\n      ctx.fill(\"evenodd\");\n\n      // Stroke pan handle\n      ctx.beginPath();\n      if (currentAction === \"pan\") {\n        ctx.strokeStyle = selectedHandleColor; //`rgba(${c}, ${c}, ${c}, 1)`;\n        ctx.lineWidth = twoPx;\n        drawPanHandlePath(handleIsNarrow, true);\n      } else {\n        ctx.strokeStyle = unselectedHandleColor; //`rgba(${c}, ${c}, ${c}, 0.75)`;\n        ctx.lineWidth = onePx;\n        drawPanHandlePath(handleIsNarrow, false);\n      }\n      ctx.stroke();\n\n      // Draw resize handles\n      const resizeHandleWidth = 8 * devicePixelRatio;\n      const handleHeight = 24 * devicePixelRatio;\n      const leftHandleColour =\n        currentAction === \"resize-left\" || state.overLeftHandle\n          ? selectedHandleColor\n          : unselectedHandleColor;\n      const rightHandleColour =\n        currentAction === \"resize-right\" || state.overRightHandle\n          ? selectedHandleColor\n          : unselectedHandleColor;\n      const halfWidth = resizeHandleWidth / 2;\n      let leftX;\n      const leftC = Math.min(\n        halfWidth,\n        halfWidth - Math.min(halfWidth, halfWidth - start)\n      );\n      let rightC = Math.min(\n        halfWidth,\n        halfWidth - Math.min(halfWidth, halfWidth - (width - end))\n      );\n      if (handleWidth > minHandleWidth * 2) {\n        leftX = Math.max(0, start - resizeHandleWidth * 0.5);\n      } else {\n        leftX =\n          Math.max(0, start - resizeHandleWidth * 0.5) +\n          (handleWidth / 2 - minHandleWidth);\n      }\n      let rightX;\n      if (handleWidth > minHandleWidth * 2) {\n        rightX =\n          Math.min(width - resizeHandleWidth, end - resizeHandleWidth * 0.5) -\n          onePx;\n      } else {\n        rightX = Math.min(\n          width - resizeHandleWidth,\n          end - resizeHandleWidth * 0.5 - (handleWidth / 2 - minHandleWidth)\n        );\n      }\n      // Draw left resize handle\n      resizeHandleAt(leftX, leftHandleColour, leftC);\n      // Draw right resize handle\n      resizeHandleAt(rightX, rightHandleColour, rightC);\n    };\n\n  const startHandleResize = (e, timelineElements, state, xOffset, action) => {\n    if (e.isPrimary && !state.currentAction) {\n      const target = e.target;\n      target.setPointerCapture(e.pointerId);\n      state.currentAction = action;\n      if (action === \"resize-left\") {\n        state.handleStartOffsetXZeroOne = state.left;\n      } else if (action === \"resize-right\") {\n        state.handleStartOffsetXZeroOne = state.right;\n      }\n      state.handleDragOffsetX = xOffset;\n      timelineElements.overlayCanvas.dispatchEvent(\n        new Event(\"interaction-begin\")\n      );\n    }\n  };\n  const endHandleResize = (e, timelineElements, state) => {\n    if (e.isPrimary) {\n      const target = e.target;\n      if (\n        state.currentAction === \"resize-left\" ||\n        state.currentAction === \"resize-right\"\n      ) {\n        target.releasePointerCapture(e.pointerId);\n        state.currentAction = null;\n        timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-end\")\n        );\n      }\n    }\n  };\n  const dragResize = (e, timelineElements, state, xOffsetZeroOne) => {\n    if (e.isPrimary && e.target.hasPointerCapture(e.pointerId)) {\n      const thisOffsetX =\n        xOffsetZeroOne -\n        (state.handleDragOffsetX - state.handleStartOffsetXZeroOne);\n      const minRange = 1 / getMaxXZoom(timelineElements.canvas.width, state);\n      if (state.currentAction === \"resize-left\") {\n        state.left = Math.max(0, Math.min(state.right, thisOffsetX));\n        if (state.right - state.left < minRange) {\n          state.left = state.right - minRange;\n        }\n      } else if (state.currentAction === \"resize-right\") {\n        state.right = Math.min(1, Math.max(state.left, thisOffsetX));\n        if (state.right - state.left < minRange) {\n          state.right = state.left + minRange;\n        }\n      }\n      state.zoomX = 1 / (state.right - state.left);\n      timelineElements.overlayCanvas.dispatchEvent(\n        new CustomEvent(\"range-change\", {\n          detail: {\n            startZeroOne: state.left,\n            endZeroOne: state.right,\n            initialRender: false,\n          },\n        })\n      );\n    }\n  };\n\n  const initTimeline = (root, sharedState, timelineElements) => {\n    const state = {\n      currentAction: null,\n      handleStartOffsetXZeroOne: undefined,\n      handleDragOffsetX: undefined,\n      pinchRaf: undefined,\n      left: 0,\n      right: 1,\n      top: 1,\n      bottom: 0,\n      pinchStarted: false,\n      panStarted: false,\n      initialPinchXLeftZeroOne: 0,\n      initialPinchXRightZeroOne: 1,\n      zoomX: 1,\n      isDarkTheme: true,\n      numAudioSamples: 0,\n      drawTimelineUI,\n      pointers: {},\n      customInteractionMode: false,\n      scrubGlobalStarted: false,\n      scrubLocalStarted: false,\n      overLeftHandle: false,\n      overRightHandle: false,\n      overPanHandle: false,\n      overSeekTrack: false,\n    };\n\n    if (sharedState.displayTimeline) {\n      state.drawTimelineUI = drawTimelineUI(timelineElements, state);\n    }\n\n    const hitTestTimeline = (xOffsetZeroOne) => {\n      //console.log(\"hitTestTimeline\", xOffsetZeroOne);\n      const resizeHandleWidthCssPx = 44;\n      const handleWidthZeroOne =\n        resizeHandleWidthCssPx /\n        (timelineElements.canvas.width / devicePixelRatio);\n\n      let leftResizeLeft = Math.max(0, state.left - handleWidthZeroOne * 0.5);\n      let leftResizeRight = leftResizeLeft + handleWidthZeroOne;\n      let rightResizeRight = Math.min(1, state.right + handleWidthZeroOne * 0.5);\n      let rightResizeLeft = rightResizeRight - handleWidthZeroOne;\n\n      // TODO: Correctly handle if the pan handle is very narrow at either extreme of the timeline.\n      const panHandleWidth = rightResizeLeft - leftResizeRight;\n      if (panHandleWidth < handleWidthZeroOne) {\n        leftResizeLeft = Math.max(\n          0,\n          state.left -\n          handleWidthZeroOne * 0.5 -\n          (handleWidthZeroOne - panHandleWidth) * 0.5\n        );\n        rightResizeRight = Math.min(\n          1,\n          state.right +\n          handleWidthZeroOne * 0.5 +\n          (handleWidthZeroOne - panHandleWidth) * 0.5\n        );\n      }\n      rightResizeLeft = rightResizeRight - handleWidthZeroOne;\n      if (leftResizeLeft === 0 && rightResizeLeft < handleWidthZeroOne) {\n        leftResizeLeft = -handleWidthZeroOne;\n      }\n      leftResizeRight = leftResizeLeft + handleWidthZeroOne;\n      rightResizeLeft = rightResizeRight - handleWidthZeroOne;\n\n      // const ctx = timelineElements.timelineUICanvas.getContext(\"2d\");\n      // ctx.save();\n      // ctx.strokeStyle = 'red';\n      // console.log(\"leftResizeLeft\", leftResizeLeft);\n      // console.log(\"rightResizeLeft\", rightResizeLeft);\n      // ctx.strokeRect(leftResizeLeft * ctx.canvas.width, 0, (leftResizeRight - leftResizeLeft) * ctx.canvas.width, ctx.canvas.height);\n      // ctx.strokeRect(rightResizeLeft * ctx.canvas.width, 0, (rightResizeRight - rightResizeLeft) * ctx.canvas.width, ctx.canvas.height);\n      // ctx.restore();\n\n      const inResizeHandleLeft =\n        xOffsetZeroOne >= leftResizeLeft && xOffsetZeroOne <= leftResizeRight;\n      const inResizeHandleRight =\n        xOffsetZeroOne >= rightResizeLeft && xOffsetZeroOne <= rightResizeRight;\n      const inMainPanHandle =\n        xOffsetZeroOne >= leftResizeLeft && xOffsetZeroOne <= rightResizeRight;\n      const inSeekTrack = !inMainPanHandle;\n      const targetChanged =\n        state.overLeftHandle !== inResizeHandleLeft ||\n        state.overRightHandle !== inResizeHandleRight ||\n        state.overPanHandle !== inMainPanHandle ||\n        state.overSeekTrack !== inSeekTrack;\n      state.overLeftHandle = inResizeHandleLeft;\n      state.overRightHandle = inResizeHandleRight;\n      state.overPanHandle = inMainPanHandle;\n      state.overSeekTrack = inSeekTrack;\n      return {\n        inResizeHandleLeft,\n        inResizeHandleRight,\n        inSeekTrack,\n        targetChanged,\n      };\n    };\n\n    if (sharedState.displayTimeline) {\n      timelineElements.timelineUICanvas.addEventListener(\"pointerdown\", (e) => {\n        e.preventDefault();\n        e.stopImmediatePropagation();\n        if (e.pointerType === \"mouse\" && e.button !== 0) {\n          // Only response to left mouse clicks\n          return;\n        }\n        const xOffsetZeroOne =\n          e.offsetX / (timelineElements.canvas.width / devicePixelRatio);\n        const {\n          inResizeHandleLeft,\n          inResizeHandleRight,\n          inSeekTrack,\n          targetChanged,\n        } = hitTestTimeline(xOffsetZeroOne);\n\n        let redrawnUI = false;\n        if (inSeekTrack) {\n          // Clicking outside handle.\n          if (!state.currentAction) {\n            clickOutsideHandle(state, timelineElements, xOffsetZeroOne);\n            redrawnUI = true;\n          }\n        } else {\n          if (inResizeHandleLeft) {\n            startHandleResize(\n              e,\n              timelineElements,\n              state,\n              xOffsetZeroOne,\n              \"resize-left\"\n            );\n          } else if (inResizeHandleRight) {\n            startHandleResize(\n              e,\n              timelineElements,\n              state,\n              xOffsetZeroOne,\n              \"resize-right\"\n            );\n          } else {\n            startHandleDrag(e, timelineElements, state, xOffsetZeroOne);\n          }\n          redrawnUI = true;\n        }\n        if (targetChanged && !redrawnUI) {\n          // Redraw UI for i.e. hover events\n          timelineElements.overlayCanvas.dispatchEvent(\n            new Event(\"interaction-target-changed\")\n          );\n        }\n      });\n      timelineElements.timelineUICanvas.addEventListener(\"pointerup\", (e) => {\n        e.preventDefault();\n        e.stopImmediatePropagation();\n        if (state.currentAction !== null) {\n          switch (state.currentAction) {\n            case \"pan\":\n              endHandleDrag(e, timelineElements, state);\n              break;\n            case \"resize-left\":\n            case \"resize-right\":\n              endHandleResize(e, timelineElements, state);\n              break;\n          }\n        }\n      });\n      timelineElements.timelineUICanvas.addEventListener(\"pointerleave\", () => {\n        state.overSeekTrack = false;\n        state.overLeftHandle = false;\n        state.overRightHandle = false;\n        state.overPanHandle = false;\n        timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-target-changed\")\n        );\n      });\n      timelineElements.timelineUICanvas.addEventListener(\"pointermove\", (e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        e.stopImmediatePropagation();\n        const xOffsetZeroOne =\n          e.offsetX / (timelineElements.timelineUICanvas.width / devicePixelRatio);\n        if (state.currentAction !== null) {\n          switch (state.currentAction) {\n            case \"pan\":\n              dragHandle(e, timelineElements, state, xOffsetZeroOne);\n              break;\n            case \"resize-left\":\n            case \"resize-right\":\n              dragResize(e, timelineElements, state, xOffsetZeroOne);\n              break;\n          }\n        } else if (e.pressure === 0 && e.pointerType === \"mouse\") {\n          const {\n            inResizeHandleLeft,\n            inResizeHandleRight,\n            inSeekTrack,\n            targetChanged,\n          } = hitTestTimeline(xOffsetZeroOne);\n\n          // Add classes to DOM for cursors?\n          if (inResizeHandleLeft || inResizeHandleRight) {\n            if (!timelineElements.timelineUICanvas.classList.contains(\"resize\")) {\n              timelineElements.timelineUICanvas.classList.add(\"resize\");\n            }\n          } else if (!inSeekTrack) {\n            if (timelineElements.timelineUICanvas.classList.contains(\"resize\")) {\n              timelineElements.timelineUICanvas.classList.remove(\"resize\");\n            }\n            timelineElements.timelineUICanvas.classList.add(\"grab\");\n          } else {\n            if (\n              timelineElements.timelineUICanvas.classList.contains(\"grab\") ||\n              timelineElements.timelineUICanvas.classList.contains(\"resize\") ||\n              timelineElements.timelineUICanvas.classList.contains(\"grabbing\")\n            ) {\n              timelineElements.timelineUICanvas.classList.remove(\n                \"grab\",\n                \"resize\",\n                \"grabbing\"\n              );\n            }\n          }\n          if (targetChanged) {\n            // Redraw UI for i.e. hover events\n            timelineElements.overlayCanvas.dispatchEvent(\n              new Event(\"interaction-target-changed\")\n            );\n          }\n        }\n      });\n    }\n\n    const hitTestScrubHandles = (x, y) => {\n      const numPointers = Object.keys(state.pointers).length;\n      const atBottom = sharedState.displayTimeline ? y > timelineElements.canvas.height / devicePixelRatio - 44 : false;\n      const atTop = sharedState.displayTimeline ? y < 44 : true;\n      let inLocalPlaybackScrubberHandle = false;\n      let inGlobalPlaybackScrubberHandle = false;\n      if (numPointers < 2 && (atTop || atBottom)) {\n        const audioProgressZeroOne = state.audioState.audioProgressZeroOne;\n        const minHandleWidth = 44;\n        const width =\n          timelineElements.mainPlayheadCanvas.width / devicePixelRatio;\n        if (\n          atTop &&\n          audioProgressZeroOne >= state.left &&\n          audioProgressZeroOne <= state.right\n        ) {\n          const localProgress =\n            (audioProgressZeroOne - state.left) / (state.right - state.left);\n          const localPlaybackLeft = Math.max(\n            0,\n            localProgress * width - minHandleWidth / 2\n          );\n          const localPlaybackRight = localPlaybackLeft + minHandleWidth;\n          if (x >= localPlaybackLeft && x <= localPlaybackRight) {\n            inLocalPlaybackScrubberHandle = true;\n          }\n        } else if (atBottom) {\n          const globalPlaybackLeft = Math.max(\n            0,\n            audioProgressZeroOne * width - minHandleWidth / 2\n          );\n          const globalPlaybackRight = globalPlaybackLeft + minHandleWidth;\n          if (x >= globalPlaybackLeft && x <= globalPlaybackRight) {\n            inGlobalPlaybackScrubberHandle = true;\n          }\n        }\n      }\n      const targetChanged =\n        state.inLocalPlaybackScrubberHandle !== inLocalPlaybackScrubberHandle ||\n        state.inGlobalPlaybackScrubberHandle !== inGlobalPlaybackScrubberHandle;\n      state.inLocalPlaybackScrubberHandle = inLocalPlaybackScrubberHandle;\n      state.inGlobalPlaybackScrubberHandle = inGlobalPlaybackScrubberHandle;\n\n      return {\n        inLocalPlaybackScrubberHandle,\n        inGlobalPlaybackScrubberHandle,\n        targetChanged,\n      };\n    };\n\n    timelineElements.timelineUICanvas.addEventListener(\"pointercancel\", (e) => {\n      e.preventDefault();\n      e.stopImmediatePropagation();\n      if (state.currentAction !== null) {\n        switch (state.currentAction) {\n          case \"pan\":\n            endHandleDrag(e, timelineElements, state);\n            break;\n          case \"resize-left\":\n          case \"resize-right\":\n            endHandleResize(e, timelineElements, state);\n            break;\n        }\n      }\n    });\n    if (sharedState.displayTimeline) {\n      timelineElements.overlayCanvas.addEventListener(\"wheel\", (e) => {\n        e.preventDefault();\n        const xOffset =\n          e.offsetX / (timelineElements.canvas.width / devicePixelRatio);\n        const dY = e.deltaY;\n        let amount;\n        if (Math.floor(dY) === dY) {\n          // This is a mousewheel event (with integer values).\n          amount = -e.deltaY * 0.001;\n        } else {\n          // This is likely a trackpad pinch event (with real number values)\n          amount = -e.deltaY * 0.01;\n        }\n        updateZoom(xOffset, amount, state, sharedState, timelineElements);\n      });\n      timelineElements.timelineUICanvas.addEventListener(\"wheel\", (e) => {\n        e.preventDefault();\n        const xOffset =\n          e.offsetX / (timelineElements.canvas.width / devicePixelRatio);\n        const dY = e.deltaY;\n        let amount;\n        if (Math.floor(dY) === dY) {\n          // This is a mousewheel event (with integer values).\n          amount = -e.deltaY * 0.001;\n        } else {\n          // This is likely a trackpad pinch event (with real number values)\n          amount = -e.deltaY * 0.01;\n        }\n        const inHandle = xOffset >= state.left && xOffset <= state.right;\n        if (inHandle) {\n          updateZoom(xOffset, amount, state, sharedState, timelineElements);\n        }\n      });\n    }\n    timelineElements.overlayCanvas.addEventListener(\"pointerdown\", (e) => {\n      if (\n        e.pointerType === \"touch\" ||\n        (e.pointerType === \"mouse\" && e.button !== -1) ||\n        e.pressure > 0\n      ) {\n        e.preventDefault();\n        let numPointers = Object.keys(state.pointers).length;\n        if (numPointers < 2) {\n          state.pointers[e.pointerId] = {\n            x: e.offsetX,\n            y: e.offsetY,\n            time: performance.now(),\n          };\n          timelineElements.overlayCanvas.setPointerCapture(e.pointerId);\n        }\n        const {inLocalPlaybackScrubberHandle, inGlobalPlaybackScrubberHandle} =\n          hitTestScrubHandles(e.offsetX, e.offsetY);\n        state.interactionStartX = e.offsetX;\n        state.interactionStartY = e.offsetY;\n        const width =\n          timelineElements.mainPlayheadCanvas.width / devicePixelRatio;\n        if (state.customInteractionMode) {\n          root.dispatchEvent(\n            new CustomEvent(\"custom-interaction-start\", {\n              bubbles: false,\n              composed: true,\n              cancelable: false,\n              detail: {\n                offsetX: e.offsetX,\n                offsetY: e.offsetY,\n                container: timelineElements.overlayCanvas,\n              },\n            })\n          );\n        }\n        if (state.customInteractionMode && state.inCustomInteraction) ; else {\n          if (inGlobalPlaybackScrubberHandle) {\n            // Drag global playtime scrubber\n            state.scrubGlobalStarted = true;\n\n            state.scrubDragOffsetX =\n              e.offsetX - state.audioState.audioProgressZeroOne * width;\n            state.startPlayheadDrag();\n            if (e.pointerType === \"mouse\") {\n              if (\n                !timelineElements.spectrogramContainer.classList.contains(\n                  \"dragging\"\n                )\n              ) {\n                timelineElements.spectrogramContainer.classList.add(\"dragging\");\n              }\n            }\n          } else if (inLocalPlaybackScrubberHandle) {\n            // Drag local playtime scrubber\n            state.scrubLocalStarted = true;\n            const localProgress =\n              (state.audioState.audioProgressZeroOne - state.left) /\n              (state.right - state.left);\n            state.scrubDragOffsetX = e.offsetX - localProgress * width;\n            state.startPlayheadDrag();\n            if (e.pointerType === \"mouse\") {\n              if (\n                !timelineElements.spectrogramContainer.classList.contains(\n                  \"dragging\"\n                )\n              ) {\n                timelineElements.spectrogramContainer.classList.add(\"dragging\");\n              }\n            }\n          }\n        }\n      }\n    });\n    timelineElements.overlayCanvas.addEventListener(\"pointerleave\", () => {\n      state.inGlobalPlaybackScrubberHandle = false;\n      state.inLocalPlaybackScrubberHandle = false;\n      timelineElements.overlayCanvas.dispatchEvent(\n        new Event(\"interaction-target-changed\")\n      );\n    });\n\n    timelineElements.overlayCanvas.addEventListener(\"pointermove\", (e) => {\n      e.preventDefault();\n      e.stopImmediatePropagation();\n      if (\n        e.pointerType === \"touch\" ||\n        (e.pointerType === \"mouse\" && e.button !== -1) ||\n        e.pressure > 0\n      ) {\n        state.pointers[e.pointerId] = {\n          x: e.offsetX,\n          y: e.offsetY,\n          time: performance.now(),\n        };\n        if (state.customInteractionMode && state.inCustomInteraction) {\n          // Do nothing.\n          root.dispatchEvent(\n            new CustomEvent(\"custom-interaction-move\", {\n              bubbles: false,\n              composed: true,\n              cancelable: false,\n              detail: {\n                offsetX: e.offsetX,\n                offsetY: e.offsetY,\n                container: timelineElements.overlayCanvas,\n              },\n            })\n          );\n        } else {\n          onPointerMove(timelineElements.overlayCanvas, state, sharedState);\n          if (state.panStarted && e.pointerType === \"mouse\") {\n            if (\n              !timelineElements.spectrogramContainer.classList.contains(\n                \"dragging\"\n              )\n            ) {\n              timelineElements.spectrogramContainer.classList.add(\"dragging\");\n            }\n          }\n        }\n      } else if (e.pointerType === \"mouse\" && e.pressure === 0) {\n        const {targetChanged} = hitTestScrubHandles(e.offsetX, e.offsetY);\n        if (targetChanged) {\n          timelineElements.overlayCanvas.dispatchEvent(\n            new Event(\"interaction-target-changed\")\n          );\n        }\n        // NOTE: Re-dispatch mousemove for user/client embed handling.\n        root.dispatchEvent(\n          new CustomEvent(\"move\", {\n            bubbles: false,\n            composed: true,\n            cancelable: false,\n            detail: {\n              offsetX: e.offsetX,\n              offsetY: e.offsetY,\n              container: timelineElements.spectrogramContainer,\n            },\n          })\n        );\n      }\n    });\n\n    const distanceBetweenPoints = (x1, y1, x2, y2) => {\n      const dX = Math.abs(x1 - x2);\n      const dY = Math.abs(y1 - y2);\n      return Math.sqrt(dX * dX + dY * dY);\n    };\n    const clampZeroOne = (x) => Math.max(0, Math.min(1, x));\n\n    const endPointerInteraction = (e) => {\n      e.preventDefault();\n      e.stopImmediatePropagation();\n      const maxDoubleClickIntervalMs = 200;\n      timelineElements.overlayCanvas.releasePointerCapture(e.pointerId);\n      let doubleClickEventEmitted = false;\n\n      let wasInCustomInteraction =\n        state.customInteractionMode && state.inCustomInteraction;\n      if (state.customInteractionMode && state.inCustomInteraction) {\n        root.dispatchEvent(\n          new CustomEvent(\"custom-interaction-end\", {\n            composed: true,\n            bubbles: false,\n            cancelable: false,\n            detail: {\n              offsetX: e.offsetX,\n              offsetY: e.offsetY,\n              container: timelineElements.overlayCanvas,\n            },\n          })\n        );\n      } else {\n        // Check if we're in a double click\n        if (\n          state.pointers[e.pointerId] &&\n          Object.keys(state.pointers).length === 1\n        ) {\n          const timeElapsed =\n            performance.now() - state.pointers[e.pointerId].time;\n          const distanceMoved = distanceBetweenPoints(\n            state.pointers[e.pointerId].x,\n            state.pointers[e.pointerId].y,\n            e.offsetX,\n            e.offsetY\n          );\n          // NOTE: This need to be a double-click, because a single click action would clash with the select action\n          //  We need to delay the select action my `maxDoubleClickInterval` to make sure\n          //  it wasn't the beginning of a double-click action.\n          if (distanceMoved < 4 * devicePixelRatio && timeElapsed < 200) {\n            clearTimeout(state.selectPending);\n            if (state.doubleClickInProgress) {\n              const timeElapsed =\n                performance.now() - state.doubleClickInProgress.time;\n              const distanceMoved = distanceBetweenPoints(\n                state.doubleClickInProgress.x,\n                state.doubleClickInProgress.y,\n                e.offsetX,\n                e.offsetY\n              );\n              if (\n                distanceMoved < 4 * devicePixelRatio &&\n                timeElapsed < maxDoubleClickIntervalMs\n              ) {\n                const offsetZeroOne =\n                  e.offsetX / (timelineElements.canvas.width / devicePixelRatio);\n                const c = clampZeroOne(offsetZeroOne);\n                const audioOffsetZeroOne =\n                  state.left + c * (state.right - state.left);\n                doubleClickEventEmitted = true;\n                timelineElements.overlayCanvas.dispatchEvent(\n                  new CustomEvent(\"double-click\", {\n                    detail: {\n                      audioOffsetZeroOne,\n                    },\n                  })\n                );\n              }\n            }\n            state.doubleClickInProgress = {\n              x: e.offsetX,\n              y: e.offsetY,\n              time: performance.now(),\n            };\n          }\n        }\n      }\n      delete state.pointers[e.pointerId];\n      const numPointers = Object.keys(state.pointers).length;\n      if (!wasInCustomInteraction) {\n        if (numPointers < 2) {\n          if (sharedState.interacting && state.pinchStarted) {\n            timelineElements.overlayCanvas.dispatchEvent(\n              new Event(\"interaction-end\")\n            );\n          }\n          state.pinchStarted = false;\n        }\n        if (numPointers < 1) {\n          if (state.scrubLocalStarted || state.scrubGlobalStarted) {\n            state.endPlayheadDrag();\n          }\n\n          const pointerMoved = () => {\n            const dX = Math.abs(state.interactionStartX - e.offsetX);\n            const dY = Math.abs(state.interactionStartY - e.offsetY);\n            return dX > 4 || dY > 4;\n          };\n          const movedEnough = pointerMoved();\n          if (\n            !movedEnough ||\n            (!state.scrubLocalStarted &&\n              !state.scrubGlobalStarted &&\n              !state.pinchStarted &&\n              (!state.panStarted || (state.panStarted && !movedEnough)))\n          ) {\n            // Clicked without moving pointer\n            if (!doubleClickEventEmitted) {\n              const x = e.offsetX;\n              const y = e.offsetY;\n              state.selectPending = setTimeout(() => {\n                root.dispatchEvent(\n                  new CustomEvent(\"select\", {\n                    bubbles: false,\n                    composed: true,\n                    cancelable: false,\n                    detail: {\n                      offsetX: x,\n                      offsetY: y,\n                      container: timelineElements.spectrogramContainer,\n                    },\n                  })\n                );\n              }, maxDoubleClickIntervalMs);\n            }\n          }\n          if (\n            timelineElements.spectrogramContainer.classList.contains(\"dragging\")\n          ) {\n            timelineElements.spectrogramContainer.classList.remove(\"dragging\");\n          }\n\n          state.panStarted = false;\n          state.scrubGlobalStarted = false;\n          state.scrubLocalStarted = false;\n          state.pinchStarted = false;\n        }\n      }\n      if (numPointers < 1 && sharedState.interacting) {\n        timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-end\")\n        );\n      }\n    };\n\n    timelineElements.overlayCanvas.addEventListener(\n      \"pointerup\",\n      endPointerInteraction\n    );\n    timelineElements.overlayCanvas.addEventListener(\n      \"pointercancel\",\n      endPointerInteraction\n    );\n\n    const curriedSetInitialZoom = (left, right, top, bottom, initial, final) =>\n      setInitialZoom(\n        left,\n        right,\n        top,\n        bottom,\n        state,\n        sharedState,\n        timelineElements,\n        initial,\n        final\n      );\n    return {\n      drawTimelineUI: drawTimelineUI(timelineElements, state),\n      timelineState: state,\n      animateToRange,\n      getMaxXZoom: () => getMaxXZoom(timelineElements.canvas.width, state),\n      getMaxYZoom: () => getMaxYZoom(timelineElements.canvas.height),\n      resetYZoom: () =>\n        animateToRange(\n          state.left,\n          state.right,\n          state.top,\n          state.bottom,\n          state.left,\n          state.right,\n          1,\n          0,\n          100,\n          (left, right, top, bottom, final) =>\n            curriedSetInitialZoom(left, right, top, bottom, false, final)\n        ),\n      setInitialZoom: curriedSetInitialZoom,\n    };\n  };\n\n  const clickOutsideHandle = (state, timelineElements, offsetXZeroOne) => {\n    const range = state.right - state.left;\n    let targetStart = Math.max(0, offsetXZeroOne - range * 0.5);\n    let targetEnd = targetStart + range;\n    if (targetEnd > 1) {\n      targetEnd = 1;\n      targetStart = targetEnd - range;\n    }\n    const initialStart = state.left;\n    const initialEnd = state.right;\n    timelineElements.overlayCanvas.dispatchEvent(new Event(\"interaction-begin\"));\n    animateToRange(\n      initialStart,\n      initialEnd,\n      state.top,\n      state.bottom,\n      targetStart,\n      targetEnd,\n      state.top,\n      state.bottom,\n      200,\n      (start, end, _top, _bottom, _final) => {\n        state.left = start;\n        state.right = end;\n        timelineElements.overlayCanvas.dispatchEvent(\n          new CustomEvent(\"range-change\", {\n            detail: {\n              startZeroOne: state.left,\n              endZeroOne: state.right,\n              initialRender: false,\n              force: false,\n            },\n          })\n        );\n      }\n    ).then(() => {\n      timelineElements.overlayCanvas.dispatchEvent(new Event(\"interaction-end\"));\n    });\n  };\n\n  const animate = async (callback) => {\n    return new Promise((resolve) => {\n      requestAnimationFrame(() => {\n        callback();\n        resolve();\n      });\n    });\n  };\n\n  const clamp = (val) => Math.min(1, Math.max(0, val));\n\n  const smoothStep = (val) => {\n    const t = clamp(val);\n    return t * t * (3.0 - 2.0 * t);\n  };\n\n  const animateToRange = async (\n    initialStart,\n    initialEnd,\n    initialTop,\n    initialBottom,\n    targetStart,\n    targetEnd,\n    targetTop,\n    targetBottom,\n    durationMs,\n    update\n  ) => {\n    // TODO: Make max duration 200ms, but if the distance between the start range and the end range\n    //  is small, make the duration relative to that.\n    const startTime = performance.now();\n    const endTime = startTime + durationMs;\n    let tt = startTime;\n    const startRange = targetStart - initialStart;\n    const endRange = targetEnd - initialEnd;\n    const topRange = targetTop - initialTop;\n    const bottomRange = targetBottom - initialBottom;\n    while (tt < endTime) {\n      await animate(() => {\n        // Map tt into zeroToOne space\n        tt = performance.now();\n        // Smoothly interpolate initialStart to targetStart over a given duration.\n        const t = smoothStep(mapRange(tt, startTime, endTime, 0, 1));\n        const startT = Math.max(0, initialStart + startRange * t);\n\n        const endT = Math.min(1, initialEnd + endRange * t);\n        const topT = Math.min(1, initialTop + topRange * t);\n        const bottomT = Math.max(0, initialBottom + bottomRange * t);\n        // update tween\n        update(startT, endT, topT, bottomT, false);\n      });\n    }\n    const forceRenderFinalFrame = true;\n    await animate(() =>\n      update(\n        targetStart,\n        targetEnd,\n        targetTop,\n        targetBottom,\n        forceRenderFinalFrame\n      )\n    );\n  };\n\n  const FFT_WIDTH = 2048;\n  const HEIGHT = FFT_WIDTH / 2; // Height needs to be at half the FFT width.\n  const numWorkers = (navigator.hardwareConcurrency || 2) - 1;\n\n  async function initWorkers(state) {\n    if (state.workers.length === 0) {\n      const remoteScriptOrigin = cdnScriptOrigin();\n      const wasmUrl = remoteScriptOrigin ? `${remoteScriptOrigin}/pkg/spectastiq_bg.wasm` : new URL(\"./pkg/spectastiq_bg.wasm\", (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('spectastiq.js', document.baseURI).href));\n      const wasmLoader = fetch(wasmUrl);\n      const initWorkers = [];\n      for (let i = 0; i < numWorkers; i++) {\n        const worker = new WorkerPromise(`fft-worker-${i}`, state);\n        state.workers.push(worker);\n      }\n      const wasm = await (await wasmLoader).arrayBuffer();\n      for (const worker of state.workers) {\n        initWorkers.push(worker.init(wasm));\n      }\n      await Promise.all(initWorkers);\n    }\n  }\n\n  const normalizeAudioBuffer = (buffer) => {\n    // Find the peak amplitude in the buffer\n    let peak = 0;\n    for (let i = 0; i < buffer.length; i++) {\n      const sample = Math.abs(buffer[i]);\n      if (sample > peak) {\n        peak = sample;\n      }\n    }\n\n    // If there's no signal, return early\n    if (peak === 0) return buffer;\n\n    // Calculate the target amplitude for -3 dBFS\n    const targetAmplitude = Math.pow(10, (-3) / 20); // Convert dB to linear scale\n\n    // Calculate scaling factor\n    const scaleFactor = targetAmplitude / peak;\n\n    // Apply normalization to all samples\n    for (let i = 0; i < buffer.length; i++) {\n      buffer[i] *= scaleFactor;\n    }\n\n    return buffer;\n  };\n\n  const getGainForRegion = (state, minZeroOne, maxZeroOne, minFreqZeroOne, maxFreqZeroOne) => {\n    const fullSpectrogram = state.imageDatas[0].imageData;\n    const sliceLen = FFT_WIDTH / 2;\n    let max = 0;\n    const numSlices = fullSpectrogram.length / sliceLen;\n    const initialNoiseSliceNum = 0;\n    const startingSlice = Math.max(initialNoiseSliceNum, Math.floor(numSlices * minZeroOne));\n    const endingSlice = Math.floor(numSlices * maxZeroOne);\n    const startSliceOffset = startingSlice * sliceLen;\n    const endSliceOffset = endingSlice * sliceLen;\n    const freqRange = Math.min(sliceLen, state.clip);\n    const minFreqCutOff = Math.floor(freqRange * minFreqZeroOne);\n    const maxFreqCutOff = Math.floor(freqRange * maxFreqZeroOne);\n    for (\n      let sliceNum = startSliceOffset;\n      sliceNum < endSliceOffset;\n      sliceNum += sliceLen\n    ) {\n      const slice = state.sharedOutputData.slice(sliceNum, sliceNum + sliceLen);\n      for (let i = minFreqCutOff; i < maxFreqCutOff; i++) {\n        const val = slice[i];\n        max = Math.max(max, val);\n      }\n    }\n    max = Math.min(max, state.max);\n    const globalMax = Math.log10(state.max);\n    const localMax = Math.log10(max);\n    return globalMax / localMax;\n  };\n\n  const initSpectrogram = async (fileBytes, previousState) => {\n    const state = {\n      sharedFloatData: undefined,\n      sharedOutputData: undefined,\n      prevLeft: undefined,\n      prevRight: undefined,\n      max: undefined,\n      imageDatas: [],\n      canvasWidth: 0,\n      pendingRender: {\n        complete: true,\n      },\n      ctxs: (previousState && previousState.ctxs) || new Map(),\n      firstRender: true,\n      colorMap: 4,\n      cropAmountTop: 0,\n      cropAmountBottom: 0,\n      workers: (previousState && previousState.workers) || [],\n    };\n    await initWorkers(state);\n    // Normalise the audio to a peak of -3 dBFS\n    const audioContext = (previousState && previousState.offlineAudioContext) || new OfflineAudioContext({\n      length: 1024 * 1024,\n      numberOfChannels: 1,\n      sampleRate: 48000,\n    });\n    // TODO: Decode audio off main thread\n    const wavData = await audioContext.decodeAudioData(fileBytes).catch((e) => {\n      console.error(e);\n    });\n    if (!wavData) {\n      return {\n        error: 'Could not decode audio data',\n      };\n    }\n    const floatData = wavData.getChannelData(0);\n    // Sometimes we get malformed files that end in zeros – we want to truncate these.\n    let actualEnd = floatData.length;\n    for (let i = floatData.length - 1; i > -1; i--) {\n      if (floatData[i] !== 0) {\n        actualEnd = i;\n        break;\n      }\n    }\n    const actualFloatData = normalizeAudioBuffer(floatData.subarray(0, actualEnd));\n    const audioFloatData = actualFloatData;\n    if (!!window.SharedArrayBuffer) {\n      state.sharedFloatData = new Float32Array(\n        new SharedArrayBuffer(actualFloatData.byteLength)\n      );\n      state.sharedFloatData.set(actualFloatData, 0);\n    } else {\n      state.sharedFloatData = actualFloatData;\n    }\n\n    const invalidateCanvasCaches = () => {\n      state.imageDatas = [];\n      state.pendingRender.complete = true;\n      state.max = undefined;\n    };\n\n    const terminateWorkers = (state) => {\n      for (const worker of state.workers) {\n        worker.terminate();\n      }\n    };\n\n    return {\n      renderRange: renderRange(state),\n      renderToContext: renderToContext(state),\n      audioFloatData,\n      invalidateCanvasCaches,\n      cyclePalette: () => cyclePalette(state),\n      terminateWorkers: () => terminateWorkers(state),\n      persistentSpectrogramState: {workers: state.workers, offlineAudioContext: audioContext, ctxs: state.ctxs},\n      getGainForRegion: (startZeroOne, endZeroOne, minFreq, maxFreq) => getGainForRegion(state, startZeroOne, endZeroOne, minFreq, maxFreq),\n    };\n  };\n\n  const drawImage = (state, ctx) => {\n    if (ctx && !state.ctxs.get(ctx)) {\n      state.ctxs.set(ctx, init(ctx));\n    }\n    return state.ctxs.get(ctx).drawImage;\n  };\n  const submitTexture = (state, ctx) => {\n    if (ctx && !state.ctxs.get(ctx)) {\n      state.ctxs.set(ctx, init(ctx));\n    }\n    return state.ctxs.get(ctx).submitTexture;\n  };\n\n  const colorMaps = [\"Viridis\", \"Plasma\", \"Inferno\", \"Grey\"];\n  const cyclePalette = (state) => {\n    state.colorMap++;\n    if (state.colorMap >= colorMaps.length) {\n      state.colorMap = 0;\n    }\n    return colorMaps[state.colorMap];\n  };\n\n  const renderRange =\n    (state) => async (startZeroOne, endZeroOne, renderWidth, force) => {\n\n      // NOTE: Min width for renders, so that narrow viewports don't get overly blurry images when zoomed in.\n      renderWidth = Math.max(1920, renderWidth);\n      if (startZeroOne === 0 && endZeroOne === 1) {\n        if (state.imageDatas.length) {\n          // We've already rendered the fully zoomed out version, no need to re-render\n          return new Promise((resolve) => resolve());\n        }\n      }\n      return new Promise((resolve) => {\n        if (\n          force ||\n          startZeroOne !== state.prevLeft ||\n          endZeroOne !== state.prevRight ||\n          renderWidth !== state.canvasWidth\n        ) {\n          // Kick off this render of the full visible region at optimal resolution, as long as it's not already processing.\n          // Once ready, stretch it as best we can to the visible region.\n          if (state.pendingRender.complete || force) {\n            state.pendingRender.complete = false;\n            // Kick off a render at the current zoom level.\n            const length = state.sharedFloatData.length;\n            const startSample = Math.floor(length * startZeroOne);\n            const endSample = Math.min(length, Math.ceil(length * endZeroOne));\n\n            renderArrayBuffer(\n              state,\n              renderWidth,\n              startZeroOne,\n              endZeroOne,\n              startSample,\n              endSample\n            ).then((s) => {\n              state.prevLeft = startZeroOne;\n              state.canvasWidth = renderWidth;\n              state.prevRight = endZeroOne;\n              state.pendingRender.complete = true;\n              resolve(s);\n            });\n          } else {\n            resolve();\n          }\n        } else {\n          resolve();\n        }\n      });\n    };\n\n  const renderToContext =\n    (state) =>\n      async (\n        ctx,\n        startZeroOne,\n        endZeroOne,\n        top,\n        bottom\n      ) => {\n        // Figure out the best intermediate render to stretch.\n        // Do we store the final coloured imagedata to stretch, or the FFT array data?\n\n        // Set best match to the full zoomed out range, then look for a better match\n        let bestMatch = state.imageDatas[0];\n        let exactMatch = false;\n        let cropLeft = startZeroOne;\n        let cropRight = endZeroOne;\n\n        // Look first for an exact match.\n        for (let i = 1; i < state.imageDatas.length; i++) {\n          const data = state.imageDatas[i];\n          if (\n            data.startZeroOne === startZeroOne &&\n            data.endZeroOne === endZeroOne\n          ) {\n            cropLeft = 0;\n            cropRight = 1;\n            // An exact match\n            exactMatch = true;\n            bestMatch = data;\n            break;\n          }\n        }\n        if (!exactMatch) {\n          // Work out whether we're zooming out or in relative to the prev frame, or panning.\n          if (state.imageDatas.length === 2) {\n            let zoomingIn = false;\n            // Look for a more zoomed out match\n            for (let i = 1; i < state.imageDatas.length; i++) {\n              const data = state.imageDatas[i];\n              if (data.startZeroOne <= startZeroOne && data.endZeroOne >= endZeroOne) {\n                zoomingIn = true;\n                // Work out what proportion of the more zoomed out image we want.\n                cropLeft = mapRange(\n                  startZeroOne,\n                  data.startZeroOne,\n                  data.endZeroOne,\n                  0,\n                  1\n                );\n                cropRight = mapRange(\n                  endZeroOne,\n                  data.startZeroOne,\n                  data.endZeroOne,\n                  0,\n                  1\n                );\n                bestMatch = data;\n                break;\n              }\n            }\n\n            if (!zoomingIn) {\n              // If zooming out, or panning, synthesise a new image\n              const prevImage = state.imageDatas[1];\n              prevImage.endZeroOne - prevImage.startZeroOne;\n            }\n          }\n        }\n        const bitmap = bestMatch;\n        if (bitmap) {\n          const end = cropLeft + (cropRight - cropLeft);\n\n          const bitmapIndex = bitmap.startZeroOne === 0 && bitmap.endZeroOne === 1 ? 0 : 1;\n\n          // If range is 0..1\n          bitmap.submitted = bitmap.submitted || new Map();\n          if (!bitmap.submitted.has(ctx)) {\n            submitTexture(state, ctx)(bitmapIndex, bitmap.imageData, bitmap.width, bitmap.height);\n            bitmap.submitted.set(ctx, true);\n          }\n          drawImage(state, ctx)(\n            bitmapIndex,\n            bitmap.normalizationScale,\n            cropLeft,\n            end,\n            top,\n            bottom,\n            state.cropAmountTop,\n            state.cropAmountBottom,\n            state.colorMap\n          );\n          return state;\n        }\n      };\n\n  const cdnScriptOrigin = () => {\n    const spectastiqIsLoadedFromCdn = Array.from(document.getElementsByTagName('script'))\n      .find(el => el.src.startsWith(\"https://cdn.jsdelivr.net/gh/hardiesoft/spectastiq\"));\n    if (spectastiqIsLoadedFromCdn) {\n      const cdnStub = spectastiqIsLoadedFromCdn.src.split(\"/\");\n      cdnStub.pop();\n      return cdnStub.join(\"/\");\n    }\n    return false;\n  };\n\n  class WorkerPromise {\n    constructor(name) {\n      this.name = name;\n      const remoteScriptOrigin = cdnScriptOrigin();\n      if (remoteScriptOrigin) {\n        // Use the bundled/non-module version of the worker using importScripts\n        this.worker = this.worker = new Worker(\n          URL.createObjectURL(\n            new Blob(\n              [\n                `importScripts(\"${remoteScriptOrigin}/worker-bundle.min.js\")`,\n              ],\n              {type: \"text/javascript\"}\n            )),\n          {type: \"classic\"}\n        );\n      } else {\n        this.worker = new Worker(\n          new URL(\"./spectastiq-worker.js\", (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('spectastiq.js', document.baseURI).href)),\n          {type: \"module\", credentials: \"same-origin\"}\n        );\n      }\n      this.worker.onmessage = ({data}) => {\n        if ((!window.SharedArrayBuffer) && data.output) {\n          // Copy outputs back to state.sharedOutputData in the correct offsets\n          this.output.subarray(data.offsets.outStart, data.offsets.outEnd).set(data.output, 0);\n        }\n        // Resolve;\n        this.work[data.id](data);\n        delete this.work[data.id];\n      };\n      this.work = {};\n      this.id = 0;\n    }\n\n    doWork(data, output) {\n      if (output) {\n        this.output = output;\n      }\n      return new Promise((resolve) => {\n        const jobId = this.id;\n        this.id++;\n        this.work[jobId] = resolve;\n        const message = {id: jobId, ...data};\n        try {\n          this.worker.postMessage(message);\n        } catch (e) {\n          console.warn(e);\n        }\n      });\n    }\n\n    init(wasm) {\n      return this.doWork({\n        type: \"Init\",\n        name: this.name,\n        wasm,\n      });\n    }\n\n    terminate() {\n      this.worker.terminate();\n    }\n  }\n\n  async function renderArrayBuffer(\n    state,\n    canvasWidth,\n    startZeroOne,\n    endZeroOne,\n    startSample,\n    endSample\n  ) {\n    const widthChanged = canvasWidth !== state.canvasWidth;\n    const numChunks = numWorkers;\n    const canvasChunkWidth = Math.ceil(canvasWidth / numChunks);\n\n    if (!state.sharedOutputData || widthChanged) {\n      if (!state.sharedOutputData || canvasWidth > state.canvasWidth) {\n        // Realloc on resize\n        if (!!window.SharedArrayBuffer) {\n          state.sharedOutputData = new Float32Array(\n            new SharedArrayBuffer(canvasChunkWidth * numChunks * 4 * HEIGHT)\n          );\n        } else {\n          state.sharedOutputData = new Float32Array(\n            new ArrayBuffer(canvasChunkWidth * numChunks * 4 * HEIGHT)\n          );\n        }\n      }\n    }\n\n    const audioChunkLength = Math.ceil((endSample - startSample) / numChunks);\n    const canvasChunkLength = canvasChunkWidth * (FFT_WIDTH / 2);\n    const job = [];\n    let chunk = 0;\n    let chunkStart = startSample;\n    let outStart = 0;\n    while (chunkStart < endSample) {\n      const chunkEnd = Math.min(chunkStart + audioChunkLength, endSample);\n      const outEnd = Math.min(\n        outStart + canvasChunkLength,\n        state.sharedOutputData.length\n      );\n\n      // Pass in 1 FFT window width as the prelude, so that we don't get a period of no output at the beginning of the slice\n      const preludeStart = Math.max(0, chunkStart - FFT_WIDTH);\n      const preludeEnd = Math.min(preludeStart + FFT_WIDTH, chunkStart);\n      const work = {\n        type: \"Process\",\n        data: state.sharedFloatData.subarray(chunkStart, chunkEnd),\n        prelude: state.sharedFloatData.subarray(preludeStart, preludeEnd),\n        offsets: {outStart, outEnd}\n      };\n      if (!!window.SharedArrayBuffer) {\n        work.output = state.sharedOutputData.subarray(outStart, outEnd);\n      }\n      job.push(\n        state.workers[chunk].doWork(work, state.sharedOutputData)\n      );\n      chunk++;\n      outStart += canvasChunkLength;\n      chunkStart += audioChunkLength;\n    }\n    // NOTE: Interestingly, if we support streaming the audio in,\n    //  we don't know what the maxes are ahead of time...\n    // NOTE: Maxes are what we use to normalise on.\n\n    // NOTE: We may *need* to support streaming audio chunks for longer audio clips?\n\n    // FIXME - Only grab the maxes once, at startup? It's possible there are smaller sounds that aren't captured at that zoom\n    //  level, and the max may need to be adjusted though.\n    await Promise.all(job);\n    if (state.firstRender) {\n      // Work out the actual clipping\n      state.firstRender = false;\n      // NOTE: Try to find the actual sample rate of the audio if it's been resampled.\n      const sliceLen = FFT_WIDTH / 2;\n      const negs = [];\n\n      // Starting 100 pixels/slices into the audio\n      for (\n        let j = sliceLen * 100;\n        j < state.sharedOutputData.length;\n        j += sliceLen\n      ) {\n        const slice = state.sharedOutputData.slice(j, j + sliceLen);\n        for (let i = slice.length - 1; i > -1; i--) {\n          // TODO: May need to tune this threshold for very quiet audio files?\n          if (slice[i] > 10000) {\n            negs.push(i);\n            break;\n          }\n        }\n      }\n      negs.sort();\n      const clip = Math.min(negs[Math.floor(negs.length / 2)], FFT_WIDTH / 2);\n      const cClip = FFT_WIDTH / 2 - clip;\n      const clipPercent = 1 - (cClip / (FFT_WIDTH / 2));\n      state.actualSampleRate = 48000 * clipPercent;\n      state.cropAmountTop = 1 - clipPercent;\n      state.clip = clip;\n\n    }\n    if (!state.max) {\n      // Maybe we need to calculate the min/maxes here after cropping anyway?\n      // We remove values at the top of the clip less than 10,000\n      const sliceLen = FFT_WIDTH / 2;\n      let min = Number.MAX_VALUE;\n      let max = 0;\n      // NOTE: Don't use the very beginning of the file, sometimes they have weird noise\n      for (\n        let j = 100;\n        j < state.sharedOutputData.length;\n        j += sliceLen\n      ) {\n        // NOTE: Crop off noise floor.\n        const slice = state.sharedOutputData.slice(j, j + sliceLen);\n        // console.log(\"noise floor\", (12 /1024) * state.actualSampleRate);\n        // TODO: Work out actual frequency cutoff of around 500hz\n        const m = Math.min(slice.length, state.clip);\n        for (let i = 12; i < m; i++) {\n          const val = slice[i];\n          min = Math.min(min, val);\n          max = Math.max(max, val);\n        }\n      }\n      state.min = min;\n      state.max = max;\n    }\n    // TODO: Once we know how we're cropping etc, work out min/max values and translate that to a volume scale.\n    //  Also allow doing this *again* for a zoomed region of interest.\n    // noinspection JSSuspiciousNameCombination\n    const nextImageData = {\n      startZeroOne,\n      endZeroOne,\n      imageData: new Float32Array(state.sharedOutputData),\n      normalizationScale: state.max,\n      width: HEIGHT,\n      height: canvasChunkWidth * numChunks,\n    };\n\n    if (state.imageDatas.length === 0) {\n      state.imageDatas = [nextImageData];\n    } else if (state.imageDatas.length === 1) {\n      state.imageDatas.push(nextImageData);\n    } else {\n      state.imageDatas[1] = nextImageData;\n    }\n    return {\n      actualSampleRate: state.actualSampleRate,\n      cropAmountTop: state.cropAmountTop,\n    };\n  }\n\n  const initAudioPlayer = (\n    root,\n    sharedState,\n    timelineState,\n    playerElements,\n  ) => {\n    const audioContext = new AudioContext({sampleRate: 48000});\n    const gainNode = audioContext.createGain();\n    const filterNode = audioContext.createBiquadFilter();\n    filterNode.type = \"allpass\";\n    const volume = 1.0;\n    setGain(gainNode, volume);\n\n    const state = {\n      audioNodes: {\n        gainNode,\n        filterNode,\n      },\n      audioContext,\n      audioProgressZeroOne: 0,\n      playbackStartOffset: 0,\n      progressSampleTime: 0,\n      playing: false,\n      audioStatusPoll: 0,\n      capturedElement: null,\n      wasPlaying: false,\n      playheadStartOffsetXZeroOne: 0,\n      playheadDragOffsetX: 0,\n      followPlayhead: false,\n      mainPlayheadStartOffsetXZeroOne: 0,\n      mainPlayheadDragOffsetX: 0,\n      dragPlayheadRaf: 0,\n      playheadWasInRangeWhenPlaybackStarted: false,\n      root,\n    };\n\n    // FIXME Maybe this should be in setup default controls\n    playerElements.playButton.addEventListener(\"click\", async () => {\n      await togglePlayback(state, timelineState, sharedState, playerElements);\n    });\n\n    return {\n      audioState: state,\n      updatePlayhead: (beganPlaying = false, rangeChange = false,) =>\n        updatePlayhead(\n          state,\n          timelineState,\n          sharedState,\n          playerElements,\n          beganPlaying,\n          rangeChange\n        ),\n      setPlaybackOffset: (offsetZeroOne) =>\n        setPlaybackTime(offsetZeroOne, state),\n      setBandPass: (minFreq, maxFreq) =>\n        setBandPass(filterNode, minFreq, maxFreq),\n      removeBandPass: () => removeBandPass(filterNode),\n      setGain: (volume) => setGain(gainNode, volume),\n      pause: () => pauseAudio(state, timelineState, sharedState, playerElements),\n      play: (startOffsetZeroOne, stopOffsetZeroOne) => playAudio(state, timelineState, sharedState, playerElements, startOffsetZeroOne, stopOffsetZeroOne),\n      togglePlayback: () => togglePlayback(state, timelineState, sharedState, playerElements),\n      startPlayheadDrag: () => startPlayheadDrag(state, timelineState, sharedState, playerElements),\n      endPlayheadDrag: () => endPlayheadDrag(state, timelineState, sharedState, playerElements),\n      dragLocalPlayhead: (x) => dragLocalPlayhead(x, state, timelineState, sharedState, playerElements),\n      dragGlobalPlayhead: (x) => dragGlobalPlayhead(x, state, timelineState, sharedState, playerElements),\n    };\n  };\n\n  const removeBandPass = (biQuadFilterNode) => {\n    // Does this really turn things off properly?\n    biQuadFilterNode.type = \"allpass\";\n  };\n\n  const setBandPass = (biQuadFilterNode, minFreq, maxFreq) => {\n    minFreq = Math.min(maxFreq, Math.max(100, minFreq));\n    biQuadFilterNode.type = \"bandpass\";\n    const freqCenter = minFreq + (maxFreq - minFreq) * 0.5;\n    const freqDelta = Math.max(100, maxFreq - minFreq);\n    // This is how \"wide\" the filter bell-curve is around the center as a ratio?\n    const QFactor = freqCenter / freqDelta;\n    biQuadFilterNode.frequency.value = freqCenter;\n    biQuadFilterNode.Q.value = QFactor;\n  };\n\n  const setGain = (gainNode, volume) => {\n    gainNode.gain.value = volume;\n    return gainNode.gain.value;\n  };\n\n  const startPlayheadDrag = (state, timelineState, sharedState, playerElements) => {\n    state.wasPlaying = state.playing;\n    if (state.playing) {\n      pauseAudio(state, timelineState, sharedState, playerElements, state.audioProgressZeroOne);\n    }\n  };\n\n  const endPlayheadDrag = (\n    state,\n    timelineState,\n    sharedState,\n    playerElements\n  ) => {\n    if (state.wasPlaying) {\n      playAudio(state, timelineState, sharedState, playerElements, state.audioProgressZeroOne).then(() => {\n        // Do nothing\n      });\n    }\n  };\n\n\n  const dragGlobalPlayhead = (xZeroOne, state, timelineState, sharedState, playerElements) => {\n    const thisOffsetXZeroOne = Math.max(\n      0,\n      Math.min(xZeroOne, 1)\n    );\n    cancelAnimationFrame(state.dragPlayheadRaf);\n    if (state.audioContext.state !== \"running\") {\n      // Update the playhead anyway.\n      state.audioProgressZeroOne = thisOffsetXZeroOne;\n      updatePlayhead(state, timelineState, sharedState, playerElements);\n    }\n    state.dragPlayheadRaf = requestAnimationFrame(async () => {\n      state.audioProgressZeroOne = thisOffsetXZeroOne;\n      setPlaybackTime(thisOffsetXZeroOne, state).then(() => {\n        updatePlayhead(state, timelineState, sharedState, playerElements);\n      });\n    });\n  };\n\n  const dragLocalPlayhead = (\n    xZeroOne,\n    state,\n    timelineState,\n    sharedState,\n    playerElements\n  ) => {\n    const range = timelineState.right - timelineState.left;\n    const thisOffsetXZeroOne = Math.min(\n      timelineState.right,\n      timelineState.left +\n      Math.max(\n        0,\n        Math.min(range * xZeroOne, 1)\n      )\n    );\n    cancelAnimationFrame(state.dragPlayheadRaf);\n    if (state.audioContext.state !== \"running\") {\n      // Update the playhead anyway.\n      state.audioProgressZeroOne = thisOffsetXZeroOne;\n      updatePlayhead(state, timelineState, sharedState, playerElements);\n    }\n    state.dragPlayheadRaf = requestAnimationFrame(async () => {\n      state.audioProgressZeroOne = thisOffsetXZeroOne;\n      //state.progressSampleTime = performance.now();\n      setPlaybackTime(thisOffsetXZeroOne, state).then(() => {\n        updatePlayhead(state, timelineState, sharedState, playerElements);\n      });\n    });\n  };\n\n  const setPlaybackTime = async (offsetZeroOne, state) => {\n    if (state.audioDuration) {\n      if (state.audioContext.state !== \"running\") {\n        await state.audioContext.resume();\n      }\n      state.audioProgressZeroOne = offsetZeroOne;\n      state.playbackStartTime = performance.now();\n      state.playbackStartOffset = offsetZeroOne;\n    }\n  };\n\n  const initAudio = (playerElements, audioFloatData, state) => {\n    state.audioDuration = audioFloatData.length / 48000;\n    const buffer = state.audioContext.createBuffer(1, audioFloatData.length, 48000);\n    buffer.copyToChannel(audioFloatData, 0);\n    state.audioBuffer = buffer;\n  };\n\n  const playAudio = async (state, timelineState, sharedState, playerElements, startAtOffsetZeroOne, stopAtOffsetZeroOne) => {\n    if (state.audioDuration - (state.audioProgressZeroOne * state.audioDuration) < 0.05) {\n      // We're right at the end of the clip, so restart playback at the beginning\n      state.audioProgressZeroOne = 0;\n    }\n    if (state.playing) {\n      pauseAudio(state, timelineState, sharedState, playerElements);\n    }\n\n    if (state.audioContext.state !== \"running\") {\n      if (navigator.audioSession) {\n        // Try to work around issue where iOS won't play audio if phone is muted.\n        // (iOS 17+)\n        navigator.audioSession.type = 'playback';\n      }\n      await state.audioContext.resume();\n    }\n    if (startAtOffsetZeroOne !== undefined) {\n      state.audioProgressZeroOne = startAtOffsetZeroOne;\n    }\n    state.audioNodes.bufferNode = state.audioContext.createBufferSource();\n    state.audioNodes.bufferNode.buffer = state.audioBuffer;\n    state.audioNodes.bufferNode\n      .connect(state.audioNodes.filterNode)\n      .connect(state.audioNodes.gainNode)\n      .connect(state.audioContext.destination);\n\n    const startOffset = state.audioProgressZeroOne * state.audioDuration;\n\n    const playbackLatency = state.audioContext.outputLatency || state.audioContext.baseLatency || 0;\n    if (stopAtOffsetZeroOne !== undefined) {\n      const endOffset = stopAtOffsetZeroOne * state.audioDuration;\n      const secondsToPlay = endOffset - startOffset;\n\n      state.endOffsetZeroOne = stopAtOffsetZeroOne;\n      state.startOffsetZeroOne = state.audioProgressZeroOne;\n\n      state.audioNodes.bufferNode.start(0, startOffset, secondsToPlay);\n      state.playbackStartTime = performance.now() + playbackLatency;\n      state.expectedPlaybackEnd = state.playbackStartTime + (secondsToPlay * 1000);\n    } else {\n      delete state.endOffsetZeroOne;\n      state.audioNodes.bufferNode.start(0, startOffset);\n      state.playbackStartTime = performance.now() + playbackLatency;\n      state.expectedPlaybackEnd = state.playbackStartTime + (state.audioDuration * 1000);\n    }\n    state.playing = true;\n    state.playbackStartOffset = state.audioProgressZeroOne;\n\n    playerElements.playButton.classList.remove(\"paused\");\n    updatePlayhead(state, timelineState, sharedState, playerElements);\n    state.root.dispatchEvent(\n      new Event(\"playback-started\", {\n        bubbles: false,\n        composed: true,\n        cancelable: false,\n      })\n    );\n  };\n  const pauseAudio = (state, timelineState, sharedState, playerElements) => {\n    if (state.playing) {\n      cancelAnimationFrame(state.audioStatusPoll);\n      state.audioStatusPoll = 0;\n      state.playing = false;\n      state.audioNodes.bufferNode.stop();\n      playerElements.playButton.classList.add(\"paused\");\n      state.root.dispatchEvent(\n        new Event(\"playback-ended\", {\n          bubbles: false,\n          composed: true,\n          cancelable: false,\n        })\n      );\n    }\n  };\n\n  const updatePlayhead = (\n    state,\n    timelineState,\n    sharedState,\n    playerElements,\n    beganPlaying = false,\n    rangeChange = false,\n  ) => {\n    const {\n      playheadCanvasCtx,\n      mainPlayheadCanvasCtx,\n    } = playerElements;\n    const lastProgress = state.audioProgressZeroOne;\n    const elapsedSincePlaybackStarted = state.playing ? (performance.now() - (state.playbackStartTime || performance.now())) / 1000 : 0;\n    if (state.playing) {\n      state.audioProgressZeroOne = Math.max(0, Math.min(1, ((state.playbackStartOffset * state.audioDuration) + elapsedSincePlaybackStarted) / state.audioDuration));\n    }\n    if (state.playing && (state.audioProgressZeroOne === 1 || performance.now() > state.expectedPlaybackEnd)) {\n      pauseAudio(state, timelineState, sharedState, playerElements);\n      if (state.endOffsetZeroOne !== undefined) {\n        state.audioProgressZeroOne = state.endOffsetZeroOne;\n      } else {\n        // Make sure we don't overshoot\n        state.audioProgressZeroOne = lastProgress;\n      }\n    }\n    const playheadWidth = 1.5 * Math.min(1, devicePixelRatio / 2);\n    const progress = state.audioProgressZeroOne;\n    if (sharedState.displayTimeline) {\n      playheadCanvasCtx.clearRect(0, 0, playheadCanvasCtx.canvas.width, playheadCanvasCtx.canvas.height);\n    }\n    mainPlayheadCanvasCtx.clearRect(0, 0, mainPlayheadCanvasCtx.canvas.width, mainPlayheadCanvasCtx.canvas.height);\n    if (!Number.isNaN(progress)) {\n      if (!rangeChange && sharedState.displayTimeline) {\n        // Redraw the minimap playhead on its canvas at the correct offset position.\n        const width = playheadCanvasCtx.canvas.width;\n        const height = playheadCanvasCtx.canvas.height;\n        playheadCanvasCtx.fillStyle = timelineState.isDarkTheme ? `rgba(204, 204, 204, 0.8)` : `rgba(0, 0, 0, 0.8)`;\n\n        // Draw the global playhead position\n        const left = Math.min(width - playheadWidth - 1, Math.max(-playheadWidth / 2, progress * width - playheadWidth / 2));\n        playheadCanvasCtx.fillRect(left, 0, playheadWidth, height);\n      }\n      {\n        const fourPx = 4 * devicePixelRatio;\n        const threePx = 3 * devicePixelRatio;\n        const tenPx = 10 * devicePixelRatio;\n        const width = mainPlayheadCanvasCtx.canvas.width;\n        const height = mainPlayheadCanvasCtx.canvas.height;\n        mainPlayheadCanvasCtx.clearRect(0, 0, width, height);\n        mainPlayheadCanvasCtx.fillStyle = timelineState.isDarkTheme ? `rgba(204, 204, 204, 1)` : `rgba(0, 0, 0, 1)`;\n        const drawPlayheadScrubHandles = (opacity) => {\n          const audioProgressZeroOne = progress;\n          const ctx = mainPlayheadCanvasCtx;\n          const startZeroOne = timelineState.left;\n          const endZeroOne = timelineState.right;\n          const height = ctx.canvas.height;\n          const center = Math.min(audioProgressZeroOne * width, width - 1);\n          if (sharedState.displayTimeline) {\n            // Draw the global playhead scrub handle\n            ctx.beginPath();\n            ctx.moveTo(center, height);\n            ctx.lineTo(center - fourPx, height - threePx);\n            ctx.lineTo(center - fourPx, height - tenPx);\n            ctx.lineTo(center + fourPx, height - tenPx);\n            ctx.lineTo(center + fourPx, height - threePx);\n            ctx.lineTo(center, height);\n            ctx.fill();\n            ctx.beginPath();\n          }\n          // TODO: Use timelineState.inGlobalPlaybackScrubberHandle and timelineState.inLocalPlaybackScrubberHandle\n          //  to show down/hover states for scrubber handles.\n          if (timelineState.isDarkTheme) {\n            ctx.fillStyle = \"rgba(0, 0, 0, 0.15)\";\n            ctx.moveTo(center, height);\n            ctx.lineTo(center - fourPx, height - threePx);\n            ctx.lineTo(center - fourPx, height - tenPx);\n            ctx.lineTo(center, height - tenPx);\n            ctx.lineTo(center, height);\n            ctx.fill();\n          } else {\n            ctx.fillStyle = \"rgba(255, 255, 255, 0.25)\";\n            ctx.moveTo(center, height);\n            ctx.lineTo(center + fourPx, height - threePx);\n            ctx.lineTo(center + fourPx, height - tenPx);\n            ctx.lineTo(center, height - tenPx);\n            ctx.lineTo(center, height);\n            ctx.fill();\n          }\n\n          {\n            ctx.fillStyle = timelineState.isDarkTheme ? `rgba(204, 204, 204, ${opacity})` : `rgba(0, 0, 0, ${opacity})`;\n            const localProgress = (audioProgressZeroOne - startZeroOne) / (endZeroOne - startZeroOne);\n            const center = localProgress * width;\n            const height = tenPx + 0.5;\n            ctx.beginPath();\n            ctx.moveTo(center, height);\n            ctx.lineTo(center - fourPx, height - threePx);\n            ctx.lineTo(center - fourPx, height - tenPx);\n            ctx.lineTo(center + fourPx, height - tenPx);\n            ctx.lineTo(center + fourPx, height - threePx);\n            ctx.lineTo(center, height);\n            ctx.fill();\n            ctx.beginPath();\n            if (timelineState.isDarkTheme) {\n              ctx.fillStyle = `rgba(0, 0, 0, ${Math.min(opacity, 0.15)})`;\n              ctx.moveTo(center, height);\n              ctx.lineTo(center - fourPx, height - threePx);\n              ctx.lineTo(center - fourPx, height - tenPx);\n              ctx.lineTo(center, height - tenPx);\n              ctx.lineTo(center, height);\n              ctx.fill();\n            } else {\n              ctx.fillStyle = `rgba(255, 255, 255, ${Math.min(opacity, 0.25)})`;\n              ctx.moveTo(center, height);\n              ctx.lineTo(center + fourPx, height - threePx);\n              ctx.lineTo(center + fourPx, height - tenPx);\n              ctx.lineTo(center, height - tenPx);\n              ctx.lineTo(center, height);\n              ctx.fill();\n            }\n          }\n        };\n\n        const playheadInRange =\n          progress >= timelineState.left && progress <= timelineState.right;\n\n        if (state.playing) {\n          state.audioStatusPoll = requestAnimationFrame(() => {\n            updatePlayhead(state, timelineState, sharedState, playerElements);\n          });\n        }\n        state.root.dispatchEvent(\n          new CustomEvent(\"playhead-update\", {\n            bubbles: false,\n            composed: true,\n            cancelable: false,\n            detail: {\n              timeInSeconds: (progress * state.audioDuration)\n            }\n          })\n        );\n        const range = timelineState.right - timelineState.left;\n        let opacity = 1;\n        if (range >= 0.75 && sharedState.displayTimeline) {\n          opacity = mapRange(range, 0.75, 1, 1, 0.1);\n        }\n        if (playheadInRange) {\n          drawPlayheadScrubHandles(opacity);\n          mainPlayheadCanvasCtx.fillStyle = timelineState.isDarkTheme ? `rgba(204, 204, 204, ${opacity})` : `rgba(0, 0, 0, ${opacity})`;\n          // Draw the local (zoomed area) playhead position\n          const pro = (progress - timelineState.left) / range;\n          const left = Math.max(0, Math.min(width - playheadWidth, pro * width - playheadWidth / 2));\n          state.followPlayhead = true;\n          mainPlayheadCanvasCtx.fillRect(left, tenPx + 0.5, playheadWidth, height);\n          // NOTE: Advance range if playhead was inside range when playback started.\n        } else if (state.followPlayhead && !sharedState.interacting) {\n          const range = timelineState.right - timelineState.left;\n          timelineState.right = Math.min(1, timelineState.right + range);\n          timelineState.left = timelineState.right - range;\n          drawPlayheadScrubHandles();\n\n          playerElements.overlayCanvas.dispatchEvent(\n            new CustomEvent(\"range-change\", {\n              detail: {\n                startZeroOne: timelineState.left,\n                endZeroOne: timelineState.right,\n              },\n            })\n          );\n        } else {\n          drawPlayheadScrubHandles(opacity);\n          state.followPlayhead = false;\n        }\n      }\n    }\n  };\n\n  const togglePlayback = async (state, timelineState, sharedState, playerElements) => {\n    if (!state.playing) {\n      await playAudio(state, timelineState, sharedState, playerElements);\n    } else {\n      pauseAudio(state, timelineState, sharedState, playerElements);\n    }\n    return state.playing;\n  };\n\n  const template = document.createElement(\"template\");\n  template.innerHTML = `\n<style>\n  * {\n    box-sizing: border-box;\n  }     \n  #container {\n    background: transparent;                          \n    display: flex;\n    position: relative;\n    flex-direction: column;\n    opacity: 1;\n    \n    -webkit-tap-highlight-color: transparent;\n    -webkit-touch-callout: none;\n    -webkit-user-select: none;   \n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;                    \n  }\n  #container:focus {\n    /*outline: none;*/\n  }\n  #container canvas {\n    opacity: 1;\n    transition: opacity 0.5s;\n  }\n  #container.loading {\n    touch-action: none;\n    user-select: none;\n    pointer-events: none; \n  }\n  #container.disabled {\n    touch-action: none;\n    user-select: none;\n    pointer-events: none; \n  }\n  #container.loading canvas {\n    opacity: 0;\n  }\n  #container.disabled canvas {\n    opacity: 0.5;\n  }\n  #spectrogram-container {\n    position: relative;\n    cursor: grab;\n  }\n  #spectrogram-container.cursor-pointer {\n    cursor: pointer; \n  }\n  #spectrogram-container.dragging {\n    cursor: grabbing; \n  } \n  #spectrogram-container.custom-interaction-mode {\n    cursor: unset;\n  }\n  #canvas-container {\n    position: relative;\n    height: 300px;\n  }\n  #spectrogram-canvas, #spectastiq-overlay-canvas, #user-overlay-canvas {\n    position: absolute;\n    top: 0;\n    left: 0;\n    margin: 0;\n    padding: 0;\n    touch-action: none;\n    user-select: none;           \n  }\n  #spectastiq-timescale-overlay-canvas {\n    position: absolute;\n    top: 0;\n    left: 0;\n    touch-action: none;\n    user-select: none;\n  }  \n  #main-playhead-canvas, #user-overlay-canvas, #spectrogram-canvas {\n    margin: 0;\n    padding: 0;\n    position: absolute;\n    touch-action: none;\n    user-select: none;\n    pointer-events: none;\n  }\n  \n  #timeline-ui-canvas.grab {\n    cursor: grab;\n  }\n  #timeline-ui-canvas.resize {\n    cursor: ew-resize;\n  }\n  #timeline-ui-canvas.grabbing {\n    cursor: grabbing;\n  }\n  #playhead-canvas, #map-canvas {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    margin: 0;\n    z-index: 1;\n    user-select: none;\n    touch-action: none;\n    pointer-events: none;\n  }\n  #timeline-ui-canvas {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    margin: 0;\n    z-index: 1;\n    touch-action: none;\n    user-select: none;\n    cursor: pointer;\n  }\n  #mini-map {         \n    height: 60px;\n    position: relative;\n    margin: 0;\n    touch-action: none;\n    user-select: none;\n  }  \n  #container:not(.audio-loaded) #mini-map, #container:not(.audio-loaded) #canvas-container {\n    pointer-events: none;    \n  }\n  #container:not(.audio-loaded) #spectrogram-container {\n    cursor: default;\n  }\n  .select-user-file, .select-user-file input, .error-message-container {\n    display: none;\n  }\n  #container.no-src .select-user-file, #container.error .error-message-container {   \n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    position: absolute;\n    display: flex;\n    align-items: center;\n    justify-content: center;  \n  }\n  .error-message {\n    background: darkred;\n    color: white;\n    font-family: sans-serif;\n    padding: 5px 5px 5px 7px;\n    border-radius: 3px;\n    font-size: 14px;\n    box-shadow: #111 0 2px 3px;\n  }\n  .error-message pre {\n    background: rgba(0, 0, 0, 0.5);\n    display: inline-block;\n    color: #ddd;\n    padding: 3px;\n    margin: 0;\n    font-size: 13px;\n    border-radius: 3px;\n  }\n  #controls {   \n    display: flex;                   \n    background: #333;\n  }\n  #controls.disabled {\n    pointer-events: none;\n  }\n  #default-controls {\n    position: relative;\n    display: flex;\n    width: 100%;\n    justify-content: space-between;\n  }\n  .control-button {\n    all: unset;\n    width: 44px;\n    height: 44px;\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    align-items: center;\n    background: transparent;\n    border-radius: 3px;\n    color: #ccc;   \n  }\n  .play-toggle.paused > .pause-icon {\n    display: none;\n  }\n  .play-toggle:not(.paused) > .play-icon {\n    display: none;\n  }\n  .play-toggle:disabled {\n    opacity: 0.5;\n  }\n     \n  .lds-ring {\n    display: inline-block;\n    position: absolute;\n    left: calc(50% - 40px);      \n    width: 80px;\n    height: 80px;\n    top: calc(50% - 40px);             \n  }\n  #progress-bar {\n    display: inline-block;\n    position: absolute;\n    left: calc(50% - 60px);      \n    width: 120px;   \n    top: calc(50% + 50px);   \n  }\n  .lds-ring div {\n    box-sizing: border-box;\n    display: block;\n    position: absolute;\n    width: 64px;\n    height: 64px;\n    margin: 8px;\n    border-width: 8px;\n    border-style: solid;\n    border-radius: 50%;\n    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    border-color: #fff transparent transparent transparent;\n  }\n  .lds-ring div:nth-child(1) {\n    animation-delay: -0.45s;\n  }\n  .lds-ring div:nth-child(2) {\n    animation-delay: -0.3s;\n  }\n  .lds-ring div:nth-child(3) {\n    animation-delay: -0.15s;\n  }\n  @keyframes lds-ring {\n    0% {\n      transform: rotate(0deg);\n    }\n    100% {\n      transform: rotate(360deg);\n    }\n  }\n  \n  .left-controls {\n    display: flex;   \n    font-family: sans-serif;\n    color: #ccc;\n    align-items: center;\n  }\n  .right-controls {\n    display: flex;\n  }\n  .time-elapsed {\n    display: inline-flex;\n    line-height: 0;\n    font-size: 14px;\n  }\n  \n  .additional-controls {\n    position: absolute;\n    right: 5px;\n    bottom: 45px;\n    box-shadow: rgba(0, 0, 0, 0.5) 0 2px 3px;\n    z-index: 1;   \n    background: #333;\n    color: #ccc;\n    font-family: Roboto, sans-serif;\n    font-size: 14px;\n    line-height: 1.2rem;\n    max-width: 192px;\n    padding-bottom: 8px;\n    display: none;\n  }\n  .additional-controls.show {\n      display: block;\n  }\n  \n  .additional-controls .btn {\n    display: flex;\n    height: 44px;\n    line-height: 44px;\n    padding: 0 10px;\n    justify-content: space-between;\n    align-items: center;\n  }\n  \n  .additional-controls .palettes {\n    display: flex;\n    flex-direction: column;\n  }\n  .additional-controls .palettes > .svg-tick {\n    display: none;\n  }\n  .additional-controls .palettes canvas {\n    border-radius: 2px;\n    margin: 0 8px;\n    width: 16px;\n    height: 16px;\n  }\n  .fullscreen-toggle {\n    display: none;\n  }\n  .fullscreen-toggle.allow-fullscreen {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;  \n  }\n  \n  /* Palette controls (radiogroup) */\n  .radio-custom {\n    opacity: 0;\n    position: absolute;   \n  }\n  \n  .radio-custom, .radio-custom-label {\n    display: inline-block;\n    vertical-align: middle;\n    margin: 5px;\n    cursor: pointer;\n  }\n  \n  .radio-custom-label {\n    line-height: 100%;\n    padding-left: 8px;\n    display: flex;\n    align-content: center;\n    position: relative;\n    text-transform: uppercase;\n    align-items: center;\n    font-size: 12px;\n  }\n  .radio-custom-label > .svg-tick {\n    margin-left: 5px;\n  }\n  .radio-custom:not(:checked) + .radio-custom-label > .svg-tick {\n    opacity: 0;\n  }\n  \n  .radio-custom:checked + .radio-custom-label > .svg-tick {  \n    opacity: 1;\n  }\n  \n  /* Toggle style checkboxes */\n  .additional-controls input[type=checkbox]{\n    height: 0;\n    width: 0;\n    visibility: hidden;\n  }\n\n  .additional-controls input[type=checkbox] + label {\n    cursor: pointer;\n    text-indent: -9999px;\n    width: 32px;\n    height: 16px;\n    background: #666;\n    display: block;\n    border-radius: 16px;\n    position: relative;\n  }\n\n  .additional-controls input[type=checkbox] + label:after {\n    content: '';\n    position: absolute;\n    top: 2px;\n    left: 2px;\n    width: 12px;\n    height: 12px;\n    background: #333;\n    border-radius: 12px;\n    transition: 0.3s;\n  }\n\n  .additional-controls input[type=checkbox]:checked + label {\n    background: #ccc;\n  }\n\n  .additional-controls input[type=checkbox]:checked + label:after {\n    left: calc(100% - 2px);\n    transform: translateX(-100%);\n  }\n\n  .additional-controls input[type=checkbox] + label:active:after {\n    width: 20px;\n  } \n  \n</style>\n<div id=\"container\">\n  <div id=\"spectrogram-container\">\n    <div id=\"canvas-container\">\n      <canvas height=\"300\" id=\"spectrogram-canvas\"></canvas>\n      <canvas height=\"300\" id=\"user-overlay-canvas\"></canvas>\n      <canvas height=\"30\" id=\"spectastiq-timescale-overlay-canvas\"></canvas>        \n      <canvas height=\"300\" id=\"spectastiq-overlay-canvas\"></canvas>\n      <canvas height=\"300\" id=\"main-playhead-canvas\"></canvas>                \n    </div>\n    <div id=\"mini-map\">\n      <canvas height=\"60\" id=\"map-canvas\"></canvas>     \n      <canvas height=\"60\" id=\"playhead-canvas\"></canvas>\n      <canvas height=\"60\" id=\"timeline-ui-canvas\"></canvas>         \n    </div>\n    <progress id=\"progress-bar\" max=\"100\"></progress>\n    <div class=\"lds-ring\" id=\"loading-spinner\">\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n    </div>   \n    <div class=\"select-user-file\">\n      <button class=\"select-user-file-btn\">Open local audio file</button>\n      <input type=\"file\" accept=\"audio/*\" class=\"select-user-file-input\" />\n    </div>\n    <div class=\"error-message-container\">\n      <span class=\"error-message\"></span>\n    </div>\n  </div>\n  <div id=\"controls\">\n    <slot name=\"player-controls\">\n      <div id=\"default-controls\">\n        <div class=\"left-controls\">\n          <button class=\"play-toggle control-button paused\" disabled id=\"play-button\">\n            <svg class=\"play-icon\" xmlns=\"http://www.w3.org/2000/svg\" height=\"16\" viewBox=\"0 0 384 512\"><path fill=\"currentColor\" d=\"M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z\"/></svg>\n            <svg class=\"pause-icon\" xmlns=\"http://www.w3.org/2000/svg\" height=\"16\" viewBox=\"0 0 320 512\"><path fill=\"currentColor\" d=\"M48 64C21.5 64 0 85.5 0 112V400c0 26.5 21.5 48 48 48H80c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H48zm192 0c-26.5 0-48 21.5-48 48V400c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H240z\"/></svg>\n          </button>\n          <span class=\"time-elapsed\"></span>\n        </div>\n        <div class=\"right-controls\">            \n          <div class=\"show-additional-controls control-button\">\n            <svg width=\"19.04\" xmlns=\"http://www.w3.org/2000/svg\" height=\"20\" viewBox=\"0 0 19.04 20\" fill=\"currentColor\">\n              <path d=\"M8.345,20C7.895,20,7.507,19.85,7.182,19.55C6.857,19.250,6.662,18.883,6.595,18.45L6.370,16.8C6.153,16.717,5.949,16.617,5.757,16.5C5.566,16.383,5.378,16.258,5.195,16.125L3.645,16.775C3.228,16.958,2.812,16.975,2.395,16.825C1.978,16.675,1.653,16.408,1.420,16.025L0.245,13.975C0.012,13.592,-0.055,13.183,0.045,12.750C0.145,12.317,0.370,11.958,0.720,11.675L2.045,10.675C2.028,10.558,2.020,10.446,2.020,10.338L2.020,9.662C2.020,9.554,2.028,9.442,2.045,9.325L0.720,8.325C0.370,8.042,0.145,7.683,0.045,7.250C-0.055,6.817,0.012,6.408,0.245,6.025L1.420,3.975C1.653,3.592,1.978,3.325,2.395,3.175C2.812,3.025,3.228,3.042,3.645,3.225L5.195,3.875C5.378,3.742,5.570,3.617,5.770,3.5C5.970,3.383,6.170,3.283,6.370,3.200L6.595,1.550C6.662,1.117,6.857,0.75,7.182,0.45C7.507,0.150,7.895,0,8.345,0L10.695,0C11.145,0,11.533,0.15,11.857,0.45C12.182,0.75,12.378,1.117,12.445,1.55L12.670,3.2C12.887,3.283,13.091,3.383,13.283,3.5C13.474,3.617,13.662,3.742,13.845,3.875L15.395,3.225C15.812,3.042,16.228,3.025,16.645,3.175C17.062,3.325,17.387,3.592,17.62,3.975L18.795,6.025C19.028,6.408,19.095,6.817,18.995,7.250C18.895,7.683,18.670,8.042,18.320,8.325L16.995,9.325C17.012,9.442,17.02,9.554,17.02,9.662L17.020,10.338C17.020,10.446,17.003,10.558,16.97,10.675L18.295,11.675C18.645,11.958,18.87,12.317,18.970,12.75C19.07,13.183,19.003,13.592,18.77,13.975L17.57,16.025C17.337,16.408,17.012,16.675,16.595,16.825C16.178,16.975,15.762,16.958,15.345,16.775L13.845,16.125C13.662,16.258,13.47,16.383,13.27,16.5C13.07,16.617,12.87,16.717,12.67,16.8L12.445,18.45C12.378,18.883,12.182,19.250,11.857,19.55C11.533,19.850,11.145,20,10.695,20L8.345,20ZL8.345,20ZM8.520,18L10.495,18L10.845,15.35C11.362,15.217,11.841,15.021,12.283,14.763C12.724,14.504,13.128,14.192,13.495,13.825L15.97,14.850L16.945,13.15L14.795,11.525C14.878,11.292,14.937,11.046,14.97,10.787C15.003,10.529,15.02,10.267,15.02,10C15.020,9.733,15.003,9.471,14.97,9.213C14.937,8.954,14.878,8.708,14.795,8.475L16.945,6.850L15.97,5.15L13.495,6.2C13.128,5.817,12.724,5.496,12.283,5.237C11.841,4.979,11.362,4.783,10.845,4.65L10.52,2L8.545,2L8.195,4.65C7.678,4.783,7.199,4.979,6.757,5.237C6.316,5.496,5.912,5.808,5.545,6.175L3.07,5.15L2.095,6.85L4.245,8.45C4.162,8.7,4.103,8.95,4.07,9.2C4.037,9.45,4.02,9.717,4.02,10C4.02,10.267,4.037,10.525,4.07,10.775C4.103,11.025,4.162,11.275,4.245,11.525L2.095,13.15L3.07,14.85L5.545,13.8C5.912,14.183,6.316,14.504,6.757,14.763C7.199,15.021,7.678,15.217,8.195,15.35L8.52,18ZL8.52,18ZM9.570,13.5C10.537,13.5,11.362,13.158,12.045,12.475C12.728,11.792,13.07,10.967,13.07,10C13.07,9.033,12.728,8.208,12.045,7.525C11.362,6.842,10.537,6.5,9.57,6.5C8.587,6.5,7.757,6.842,7.083,7.525C6.408,8.208,6.07,9.033,6.07,10C6.07,10.967,6.408,11.792,7.083,12.475C7.757,13.158,8.587,13.5,9.57,13.5ZL9.570,13.5Z\" />\n            </svg>\n          </div>                 \n          <div class=\"additional-controls\">\n            <div class=\"btn\">\n              <span>Frequency scale</span>\n              <input type=\"checkbox\" id=\"freq-switch\" /><label for=\"freq-switch\">Toggle</label>\n            </div>\n            <div class=\"btn\">\n              <span>Time scale</span>\n              <input type=\"checkbox\" id=\"time-switch\" /><label for=\"time-switch\">Toggle</label>\n            </div>\n            <div class=\"btn\">\n              <span>Palette</span>\n            </div>\n            <div class=\"palettes\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" class=\"svg-tick\" fill=\"currentColor\">                           \n                <path d=\"M9.550,15.150L18.025,6.675C18.225,6.475,18.458,6.375,18.725,6.375C18.992,6.375,19.225,6.475,19.425,6.675C19.625,6.875,19.725,7.113,19.725,7.388C19.725,7.663,19.625,7.900,19.425,8.100L10.250,17.300C10.050,17.500,9.817,17.600,9.550,17.600C9.283,17.600,9.050,17.500,8.850,17.300L4.550,13.000C4.350,12.800,4.254,12.563,4.263,12.288C4.271,12.013,4.375,11.775,4.575,11.575C4.775,11.375,5.013,11.275,5.288,11.275C5.563,11.275,5.800,11.375,6.000,11.575L9.550,15.150Z\"></path>                                        \n              </svg>\n            </div>                  \n          </div>\n          <button class=\"fullscreen-toggle control-button\">\n            <svg width=\"20\" height=\"22.1\" viewBox=\"0 0 24 22.1\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\">\n              <path d=\"M6.6 2.6h-4v3H0V0h6.6v2.6ZM17.4 0H24v5.6h-2.6v-3h-4V0Zm0 19.5h4v-3H24V22h-6.6v-2.6ZM6.6 22.1H0v-5.6h2.6v3h4v2.6Z\" />\n            </svg>\n          </button>   \n        </div>\n      </div>\n    </slot>\n  </div>\n</div>\n`;\n\n  class Spectastiq extends HTMLElement {\n    constructor() {\n      super();\n      this.attachShadow({mode: \"open\"});\n    }\n\n    inited = false;\n    sharedState = { interacting: false, displayTimeline: false };\n\n    static observedAttributes = [\n      \"src\",\n      \"height\",\n      \"time-scale\",\n      \"frequency-scale\",\n      \"color-scheme\",\n      \"colour-scheme\",\n    ];\n\n    attributeChangedCallback(name, oldValue, newValue) {\n      if (this.inited) {\n        switch (name) {\n          case \"src\": {\n            newValue && this.loadSrc(newValue);\n          }\n            break;\n          case \"height\": {\n            const totalHeight = Number(newValue) || 360;\n            this.timelineHeight = Math.min(60, Math.max(44, totalHeight - 200));\n            this.spectrogramHeight = totalHeight - this.timelineHeight;\n            // Resize.\n            this.resizeCanvases(undefined, false);\n          }\n            break;\n          case \"time-scale\": {\n            this.drawTimescale = this.timeScale;\n            if (this.drawTimescale) {\n              this.redrawTimescaleOverlay();\n            } else {\n              this.clearTimescaleOverlay();\n            }\n          }\n            break;\n          case \"frequency-scale\": {\n            this.drawFrequencyScale = this.frequencyScale;\n            if (this.drawFrequencyScale) {\n              this.redrawFrequencyScaleOverlay();\n            } else {\n              this.clearOverlay();\n            }\n            if (this.drawTimescale) {\n              this.redrawTimescaleOverlay();\n            }\n          }\n            break;\n          case \"color-scheme\":\n          case \"colour-scheme\": {\n            if (\n              !colorMaps\n                .map((p) => p.toLowerCase())\n                .includes(newValue.toLowerCase())\n            ) {\n              console.error(\n                `Unknown color scheme: ${newValue}. Allowed schemes are any of '${colorMaps.join(\n                \"', '\"\n              )}'`\n              );\n              return;\n            }\n            while (\n              this.nextPalette().toLowerCase() !==\n              this.colorScheme.toLowerCase()\n              ) {\n              // Cycle until we get the desired palette\n            }\n          }\n            break;\n        }\n      }\n    }\n\n    listColorSchemes() {\n      return colorMaps;\n    }\n\n    connectedCallback() {\n      this.init();\n    }\n\n    disconnectedCallback() {\n      this.terminateWorkers && this.terminateWorkers();\n      this.terminateWorkers = null;\n      this.pause();\n      this.unload();\n    }\n\n    get src() {\n      return this.getAttribute(\"src\");\n    }\n\n    set src(newValue) {\n      this.setAttribute(\"src\", newValue);\n    }\n\n    get height() {\n      return this.getAttribute(\"height\");\n    }\n\n    set height(newValue) {\n      this.setAttribute(\"height\", newValue);\n    }\n\n    get timeScale() {\n      if (this.hasAttribute(\"time-scale\")) {\n        const scale = this.getAttribute(\"time-scale\");\n        return scale !== \"false\" && scale !== \"0\" && scale !== null;\n      }\n      return false;\n    }\n\n    set timeScale(newValue) {\n      this.setAttribute(\"time-scale\", newValue);\n    }\n\n    get allowFullscreen() {\n      if (this.hasAttribute(\"allow-fullscreen\")) {\n        const fullscreen = this.getAttribute(\"allow-fullscreen\");\n        return fullscreen !== \"false\" && fullscreen !== \"0\" && fullscreen !== null;\n      }\n      return false;\n    }\n\n    set allowFullscreen(newValue) {\n      this.setAttribute(\"allow-fullscreen\", newValue);\n    }\n\n    get frequencyScale() {\n      if (this.hasAttribute(\"frequency-scale\")) {\n        const scale = this.getAttribute(\"frequency-scale\");\n        return scale !== \"false\" && scale !== \"0\" && scale !== null;\n      }\n      return false;\n    }\n\n    set frequencyScale(newValue) {\n      this.setAttribute(\"frequency-scale\", newValue);\n    }\n\n    get colorScheme() {\n      return (\n        this.getAttribute(\"color-scheme\") || this.getAttribute(\"colour-scheme\") || Object.keys(COLOR_MAPS)[0]\n      );\n    }\n\n    set colorScheme(newValue) {\n      // TODO: Make sure it's an allowed colour scheme, otherwise throw a warning.\n      this.setAttribute(\"color-scheme\", newValue);\n    }\n\n    set colourScheme(newValue) {\n      // TODO: Make sure it's an allowed colour scheme, otherwise throw a warning.\n      this.setAttribute(\"color-scheme\", newValue);\n    }\n\n    unload() {\n      this.requestAborted = true;\n    }\n\n    loadSrc(src) {\n      const startTimeOffset = Number(this.getAttribute(\"start\")) || 0;\n      const endTimeOffset = Number(this.getAttribute(\"end\")) || 1;\n      const delegateDoubleClick = this.getAttribute(\"delegate-double-click\");\n      this.applicationHandlesDoubleClick = delegateDoubleClick !== \"false\" && delegateDoubleClick !== \"0\" && delegateDoubleClick !== null;\n      const requestHeaders = this.getAttribute(\"request-headers\");\n      let headers = {};\n      if (requestHeaders) {\n        try {\n          headers = JSON.parse(requestHeaders);\n        } catch (e) {\n          console.error(`Malformed JSON passed for request headers: ${e}`);\n        }\n      }\n      const {drawTimelineUI, timelineState, setInitialZoom} = this.timeline;\n      const {audioState, updatePlayhead} = this.audioPlayer;\n      const canvas = this.timelineElements.canvas;\n      const mapCtx = this.timelineElements.mapCanvas.getContext(\"webgl2\");\n      const ctx = canvas.getContext(\"webgl2\");\n      const timescaleOverlayContext =\n        this.timelineElements.timescaleCanvas.getContext(\"2d\");\n      const userOverlayCtx =\n        this.timelineElements.userOverlayCanvas.getContext(\"2d\");\n      this.progressBar.setAttribute(\"value\", String(0));\n\n      const MAX_ZOOMED_REGION = 0.8;\n      const TEXTURE_HEIGHT = 1024;\n      const map = (value, min1, max1, min2, max2) => {\n        return min2 + ((value - min1) * (max2 - min2)) / (max1 - min1);\n      };\n      this.sharedState.interacting = false;\n      (async () => {\n        if (audioState && audioState.playing) {\n          this.pause();\n        }\n        this.unload();\n        this.beginLoad();\n\n        // Download audio file and update the progress bar.\n        let fileBytes;\n        {\n          const chunks = [];\n          let receivedLength = 0;\n          this.requestAborted = false;\n          if (this.abortController) {\n            this.abortController.abort(\"User aborted\");\n          }\n          this.abortController = new AbortController();\n          this.abortController.signal.addEventListener(\"onabort\", () => {\n            this.requestAborted = true;\n          });\n          const requestInfo = {\n            mode: \"cors\",\n            cache: \"no-cache\",\n            method: \"get\",\n            signal: this.abortController.signal,\n            headers: {\n              ...headers,\n            },\n          };\n          let downloadAudioResponse;\n          try {\n            downloadAudioResponse = await fetch(src, requestInfo);\n            if (!downloadAudioResponse.ok) {\n              this.showErrorMessage(`Audio file not found <pre>${src}</pre>`);\n            } else {\n              if (!this.inited) {\n                // Put this after the fetch otherwise we can never catch it fast enough.\n                // FIXME: Even so, this doesn't fire in chrome for listeners in time.\n                this.shadowRoot.dispatchEvent(\n                  new Event(\"ready\", {\n                    composed: true,\n                    bubbles: false,\n                    cancelable: false,\n                  })\n                );\n                this.inited = true;\n              }\n              const reader = downloadAudioResponse.body.getReader();\n              let expectedLength = parseInt(\n                downloadAudioResponse.headers.get(\"Content-Length\"),\n                10\n              );\n              if (isNaN(expectedLength)) {\n                expectedLength = parseInt(\n                  downloadAudioResponse.headers.get(\"Fallback-Content-Length\"),\n                  10\n                );\n              }\n              if (!isNaN(expectedLength)) {\n                if (!this.progressBar.parentElement) {\n                  this.timelineElements.spectrogramContainer.appendChild(\n                    this.progressBar\n                  );\n                }\n              }\n              while (!this.requestAborted) {\n                const {done, value} = await reader.read();\n                if (done) {\n                  break;\n                }\n                chunks.push(value);\n                receivedLength += value.length;\n                if (!isNaN(expectedLength)) {\n                  const progress = receivedLength / expectedLength;\n                  this.progressBar.setAttribute(\"value\", String(progress * 100));\n                  if (progress === 1) {\n                    if (this.progressBar.parentElement) {\n                      this.progressBar.parentElement.removeChild(this.progressBar);\n                    }\n                  }\n                }\n              }\n              const fileBytesReceived = new Uint8Array(receivedLength);\n              let position = 0;\n              for (const chunk of chunks) {\n                fileBytesReceived.set(chunk, position);\n                position += chunk.length;\n              }\n              fileBytes = fileBytesReceived.buffer;\n              const spectrogramInited = await initSpectrogram(\n                fileBytes,\n                this.persistentSpectrogramState || null\n              );\n              if (spectrogramInited.error) {\n                this.showErrorMessage(`${spectrogramInited.error} for <pre>${this.localSrc || src}</pre>`);\n              } else {\n                const {\n                  renderRange,\n                  renderToContext,\n                  audioFloatData,\n                  invalidateCanvasCaches,\n                  terminateWorkers,\n                  cyclePalette,\n                  getGainForRegion,\n                  persistentSpectrogramState,\n                } = spectrogramInited;\n                this.getGainForRegionOfInterest = getGainForRegion;\n                timelineState.numAudioSamples = audioFloatData.length;\n                timelineState.left = 0;\n                timelineState.top = 1;\n                timelineState.bottom = 0;\n                timelineState.right = 1;\n                timelineState.zoomX = 1;\n                timelineState.pinchStarted = false;\n                timelineState.panStarted = false;\n                timelineState.initialPinchXLeftZeroOne = 0;\n                timelineState.initialPinchXRightZeroOne = 1;\n                timelineState.currentAction = null;\n                audioState.followPlayhead = false;\n                audioState.audioProgressZeroOne = 0;\n                audioState.playbackStartOffset = 0;\n                audioState.audioProgressSampleTime = performance.now();\n                audioState.wasPlaying = false;\n                audioState.playheadStartOffsetXZeroOne = 0;\n                audioState.playheadDragOffsetX = 0;\n                audioState.mainPlayheadStartOffsetXZeroOne = 0;\n                audioState.mainPlayheadDragOffsetX = 0;\n                audioState.dragPlayheadRaf = 0;\n                audioState.playheadWasInRangeWhenPlaybackStarted = false;\n\n                this.persistentSpectrogramState = persistentSpectrogramState;\n                this.terminateWorkers = terminateWorkers;\n                this.invalidateCanvasCaches = invalidateCanvasCaches;\n                this.renderRange = renderRange;\n\n                const defaultPalette = \"Viridis\";\n                // Select starting palette\n                let palette = this.colorScheme || defaultPalette;\n                if (\n                  !colorMaps\n                    .map((p) => p.toLowerCase())\n                    .includes(palette.toLowerCase())\n                ) {\n                  console.error(\n                    `Unknown color scheme: ${palette}. Allowed schemes are any of '${colorMaps.join(\n                    \"', '\"\n                  )}'`\n                  );\n                  palette = defaultPalette;\n                }\n                let paletteChangeTimeout;\n                this.nextPalette = () => {\n                  this.clearOverlay();\n                  const nextPalette = cyclePalette();\n                  // Give downstream renderers a moment to adjust to palette changes;\n                  clearTimeout(paletteChangeTimeout);\n                  paletteChangeTimeout = setTimeout(() => {\n                    timelineState.isDarkTheme = nextPalette !== \"Grey\";\n                    const startZeroOne = timelineState.left;\n                    const endZeroOne = timelineState.right;\n                    const top = timelineState.top;\n                    const bottom = timelineState.bottom;\n                    if (this.sharedState.displayTimeline) {\n                      drawTimelineUI(\n                        startZeroOne,\n                        endZeroOne,\n                        timelineState.currentAction\n                      );\n                    }\n                    renderToContext(ctx, startZeroOne, endZeroOne, top, bottom);\n                    if (this.sharedState.displayTimeline) {\n                      renderToContext(mapCtx, 0, 1, 1, 0);\n                    }\n                    audioState.progressSampleTime = performance.now();\n                    updatePlayhead();\n                    if (this.actualSampleRate) {\n                      if (this.drawFrequencyScale) {\n                        this.redrawFrequencyScaleOverlay();\n                      } else {\n                        this.clearOverlay();\n                      }\n                    }\n                  }, 10);\n                  return nextPalette;\n                };\n                while (this.nextPalette().toLowerCase() !== palette.toLowerCase()) {\n                  // Cycle until we get the desired palette\n                }\n                this.transformY = (y) => {\n                  const top = timelineState.top;\n                  const bottom = timelineState.bottom;\n                  // Is the incoming y within the clampedRangeY?\n                  // FIXME: In the shader, we need to do this also so that the texture stretches correctly.\n                  const maxTextureHeight = Math.max(canvas.height, TEXTURE_HEIGHT);\n                  const maxZoom = (maxTextureHeight / (canvas.height / window.devicePixelRatio));\n                  const maxYZoom = maxZoom * MAX_ZOOMED_REGION;\n                  // const maxYZoom =\n                  //   (TEXTURE_HEIGHT / (canvas.height / window.devicePixelRatio)) *\n                  //   MAX_ZOOMED_REGION;\n                  const rangeY = top - bottom;\n                  const minRangeY = 1 / maxZoom;\n\n                  // The input height of the selected region.\n                  const clampedRangeY = Math.max(rangeY, minRangeY);\n                  const posY = bottom / Math.max(0.000001, 1 - rangeY);\n                  const rem = 1 - clampedRangeY;\n                  const inBottom = rem * posY;\n                  const inTop = inBottom + clampedRangeY;\n\n                  const mMaxZoom = map(clampedRangeY, 1, minRangeY, 1, 1 / maxYZoom);\n                  const actualHeight = clampedRangeY * (1 / mMaxZoom);\n                  const remainder = 1 - actualHeight;\n                  const selectedBottom = remainder * posY;\n                  const selectedTop = selectedBottom + actualHeight;\n\n                  const aboveRange = y > inTop;\n                  const belowRange = y < inBottom;\n                  const inRange = y <= inTop && y >= inBottom;\n                  if (inRange) {\n                    y = map(y, inBottom, inTop, selectedBottom, selectedTop);\n                  } else if (belowRange) {\n                    y = map(y, 0, inBottom, 0, selectedBottom);\n                  } else if (aboveRange) {\n                    y = map(y, inTop, 1.0, selectedTop, 1.0);\n                  }\n                  return y;\n                };\n                this.inverseTransformY = (yZeroOne) => {\n                  // How much to crop of the top and bottom of the spectrogram (used if the sample rate of the audio was different\n                  // from the sample rate the FFT was performed at, since that leaves a blank space at the top)\n                  let y = 1 - Math.min(1, Math.max(0, yZeroOne));\n                  const top = timelineState.top;\n                  const bottom = timelineState.bottom;\n\n                  const maxTextureHeight = Math.max(canvas.height, TEXTURE_HEIGHT);\n                  const maxZoom = (maxTextureHeight / (canvas.height / window.devicePixelRatio));\n                  const maxYZoom = maxZoom * MAX_ZOOMED_REGION;\n                  const minRangeY = 1.0 / maxZoom;\n                  const rangeY = top - bottom;\n                  const clampedRangeY = Math.max(rangeY, minRangeY);\n                  // Prevent divide by zero\n                  const posY = bottom / Math.max(0.000001, 1.0 - rangeY);\n                  const mMaxZoom = map(\n                    clampedRangeY,\n                    1.0,\n                    minRangeY,\n                    1.0,\n                    1.0 / maxYZoom\n                  );\n                  const actualHeight = clampedRangeY * (1.0 / mMaxZoom);\n                  const remainder = 1.0 - actualHeight;\n                  const selectedBottom = remainder * posY;\n                  const selectedTop = selectedBottom + actualHeight;\n                  const aboveRange = y > selectedTop;\n                  const belowRange = y < selectedBottom;\n                  const inRange = y <= selectedTop && y >= selectedBottom;\n\n                  if (inRange) {\n                    y = map(y, selectedBottom, selectedTop, bottom, top);\n                  } else if (belowRange) {\n                    y = map(y, 0.0, selectedBottom, 0.0, bottom);\n                  } else if (aboveRange) {\n                    y = map(y, selectedTop, 1.0, top, 1.0);\n                  }\n                  return y;\n                };\n                let cropAmountTop = 0;\n                const clampZeroOne = (x) => Math.max(0, Math.min(1, x));\n                const clampZeroMax = (x, max) => Math.max(0, Math.min(max, x));\n                const redrawTimescaleOverlay = (\n                  ctx,\n                  startZeroOne,\n                  endZeroOne,\n                  duration\n                ) => {\n                  ctx.save();\n                  // Draw a notch every ~second\n                  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n                  const startTime = startZeroOne * duration;\n                  const endTime = endZeroOne * duration;\n                  const startSeconds = Math.floor(startTime);\n                  const endSeconds = Math.ceil(endTime);\n                  const zoomLevel = endZeroOne - startZeroOne;\n\n                  //const distanceBetweenSecondNotches = (startZeroOne + 1) / duration * ctx.canvas.width / devicePixelRatio;\n\n                  // TODO: Fade between.  I guess we want a good way of generalising this pattern of having features fade in at\n                  //  various zoom levels.  Maybe show every 10 seconds, then every 5, then every 2, then every 1\n\n                  //console.log(\"distanceBetweenSecondNotches\", distanceBetweenSecondNotches, drawEveryOtherSecond);\n                  const tenPx = 10 * devicePixelRatio;\n                  const twentyPx = tenPx * 2;\n                  const fortyPx = tenPx * 4;\n                  ctx.font = `${tenPx}px sans-serif`;\n                  ctx.textAlign = \"center\";\n                  let xOpacity = 0.5;\n                  const n1 = mapRange(0 / duration, startZeroOne, endZeroOne, 0, 1) *\n                    ctx.canvas.width / devicePixelRatio;\n                  const n2 = mapRange(1 / duration, startZeroOne, endZeroOne, 0, 1) *\n                    ctx.canvas.width / devicePixelRatio;\n                  const distanceBetweenSecondNotches = n2 - n1;\n                  const maxOpacity = 0.5;\n                  const breakPointsPx = [2, 4, 8, 16, 32, 64, 128, 256];\n                  // Work out the zoom levels these correspond to:\n                  for (let i = startSeconds; i < endSeconds; i += 1) {\n                    const m0 = (i) % 64 === 0;\n                    const m1 = (i) % 32 === 0;\n                    const m2 = (i) % 16 === 0;\n                    const m3 = (i) % 8 === 0;\n                    const m4 = (i) % 4 === 0;\n                    const m5 = (i) % 2 === 0;\n                    let displayNotch = false;\n                    let initialXOpacity = 0;\n                    if (distanceBetweenSecondNotches < breakPointsPx[0]) { // display a notch every 16 px\n                      displayNotch = m0;\n                    } else if (distanceBetweenSecondNotches < breakPointsPx[1]) {\n                      displayNotch = m0 || m1;\n                    } else if (distanceBetweenSecondNotches < breakPointsPx[2]) {\n                      displayNotch = m0 || m1 || m2;\n                    } else if (distanceBetweenSecondNotches < breakPointsPx[3]) {\n                      displayNotch = m0 || m1 || m2 || m3;\n                    } else if (distanceBetweenSecondNotches < breakPointsPx[4]) {\n                      displayNotch = m0 || m1 || m2 || m3 || m4;\n                    } else if (distanceBetweenSecondNotches < breakPointsPx[5]) {\n                      displayNotch = m0 || m1 || m2 || m3 || m4 || m5;\n                    } else {\n                      displayNotch = true;\n                    }\n                    if (displayNotch) {\n                      if (m0) {\n                        initialXOpacity = maxOpacity;\n                      } else if (m1) {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[0], breakPointsPx[1], 0, maxOpacity), maxOpacity);\n                      } else if (m2) {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[1], breakPointsPx[2], 0, maxOpacity), maxOpacity);\n                      } else if (m3) {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[2], breakPointsPx[3], 0, maxOpacity), maxOpacity);\n                      } else if (m4) {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[3], breakPointsPx[4], 0, maxOpacity), maxOpacity);\n                      } else if (m5) {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[4], breakPointsPx[5], 0, maxOpacity), maxOpacity);\n                      } else {\n                        initialXOpacity = clampZeroMax(mapRange(distanceBetweenSecondNotches, breakPointsPx[5], breakPointsPx[6], 0, maxOpacity), maxOpacity);\n                      }\n                    }\n                    // Work out whether we want to draw this notch or not at this zoom level.\n                    if (i !== 0 && displayNotch) {\n                      const oX =\n                        mapRange(i / duration, startZeroOne, endZeroOne, 0, 1) *\n                        ctx.canvas.width;\n                      xOpacity = initialXOpacity;\n                      if (this.drawFrequencyScale && oX > canvas.width - fortyPx) {\n                        xOpacity = clampZeroOne(mapRange(oX, canvas.width - fortyPx, canvas.width - twentyPx, 1, 0)) * initialXOpacity;\n                      }\n                      ctx.fillStyle = `rgba(255, 255, 255, ${xOpacity})`;\n                      // Draw notches\n                      ctx.fillRect(oX, 0, 1, ctx.canvas.height / 2);\n                      ctx.fillStyle = `rgba(255, 255, 255, ${xOpacity})`;\n                      ctx.fillText(this.formatTime(i), oX, ctx.canvas.height / 2 + tenPx);\n\n                    }\n                    // Drawing inter-second notches\n                    const opacity = clampZeroOne(\n                      mapRange(distanceBetweenSecondNotches, breakPointsPx[6], breakPointsPx[7], 0, 1)\n                    );\n                    if (opacity > 0) {\n                      ctx.fillStyle = `rgba(255, 255, 255, ${opacity * xOpacity})`;\n                      for (let j = 1; j < 10; j += 1) {\n                        const oX =\n                          mapRange(\n                            (i + 0.1 * j) / duration,\n                            startZeroOne,\n                            endZeroOne,\n                            0,\n                            1\n                          ) * ctx.canvas.width;\n                        let h = ctx.canvas.height / 3;\n                        if (j === 5) {\n                          h += 5 * devicePixelRatio;\n                        }\n                        ctx.fillRect(oX, 0, 1, h);\n                      }\n                    }\n                  }\n                  ctx.restore();\n                };\n                this.redrawTimescaleOverlay = () =>\n                  redrawTimescaleOverlay(\n                    timescaleOverlayContext,\n                    timelineState.left,\n                    timelineState.right,\n                    audioState.audioDuration\n                  );\n                this.clearTimescaleOverlay = () => {\n                  timescaleOverlayContext.save();\n                  timescaleOverlayContext.clearRect(\n                    0,\n                    0,\n                    ctx.canvas.width,\n                    ctx.canvas.height\n                  );\n                };\n\n                this.render = ({detail: {initialRender, force}}) => {\n                  if (this.raf) {\n                    cancelAnimationFrame(this.raf);\n                    this.raf = undefined;\n                  }\n                  this.raf = requestAnimationFrame(() => {\n                    const startZeroOne = timelineState.left;\n                    const endZeroOne = timelineState.right;\n                    const top = timelineState.top;\n                    const bottom = timelineState.bottom;\n                    if (\n                      (this.deferredWidth &&\n                        ctx.canvas.width !== this.deferredWidth) ||\n                      (this.deferredHeight &&\n                        ctx.canvas.height !== this.deferredHeight)\n                    ) {\n                      this.resizeCanvases(this.deferredWidth, true);\n                      this.timelineElements.container.classList.remove(\"disabled\");\n                    } else if (\n                      this.deferredWidth &&\n                      ctx.canvas.width === this.deferredWidth &&\n                      this.deferredHeight &&\n                      ctx.canvas.height === this.deferredHeight\n                    ) {\n                      this.timelineElements.container.classList.remove(\"disabled\");\n                    }\n\n                    if (this.sharedState.displayTimeline) {\n                      drawTimelineUI(\n                        startZeroOne,\n                        endZeroOne,\n                        timelineState.currentAction\n                      );\n                    }\n                    if (!audioState.playing) {\n                      audioState.progressSampleTime = performance.now();\n                      updatePlayhead();\n                    }\n\n                    // TODO: Move somewhere sensible\n                    this.drawTimescale = this.timeScale;\n                    this.drawFrequencyScale = this.frequencyScale;\n                    if (this.drawTimescale) {\n                      redrawTimescaleOverlay(\n                        timescaleOverlayContext,\n                        startZeroOne,\n                        endZeroOne,\n                        audioState.audioDuration\n                      );\n                    }\n\n                    // Render the stretched version\n                    renderToContext(ctx, startZeroOne, endZeroOne, top, bottom).then(\n                      (s) => {\n                        if (!!s) {\n                          this.shadowRoot.dispatchEvent(\n                            new CustomEvent(\"render\", {\n                              bubbles: false,\n                              composed: true,\n                              cancelable: false,\n                              detail: {\n                                range: {\n                                  begin: startZeroOne,\n                                  end: endZeroOne,\n                                  min: bottom,\n                                  max: top,\n                                },\n                                context: userOverlayCtx,\n                              },\n                            })\n                          );\n                        }\n                      }\n                    );\n                    if (initialRender) {\n                      renderToContext(mapCtx, 0, 1, 1, 0);\n                    }\n                    if (!this.sharedState.interacting || initialRender) {\n                      // Render the fine detail of the zoom level and then fill it in when available.\n                      renderRange(startZeroOne, endZeroOne, canvas.width, force).then(\n                        (rangeCropInfo) => {\n                          if (rangeCropInfo && rangeCropInfo.cropAmountTop) {\n                            cropAmountTop = rangeCropInfo.cropAmountTop;\n                            this.actualSampleRate = rangeCropInfo.actualSampleRate;\n                          }\n                          if (initialRender) {\n                            renderToContext(mapCtx, 0, 1, 1, 0).then(() => {\n                              if (\n                                this.loadingSpinner &&\n                                this.loadingSpinner.parentElement\n                              ) {\n                                this.loadingSpinner.parentElement.removeChild(\n                                  this.loadingSpinner\n                                );\n                                this.playerElements.playButton.removeAttribute(\n                                  \"disabled\"\n                                );\n                                this.shadowRoot.dispatchEvent(\n                                  new CustomEvent(\"audio-loaded\", {\n                                    composed: true,\n                                    bubbles: false,\n                                    cancelable: false,\n                                    detail: {\n                                      sampleRate: this.actualSampleRate,\n                                      duration: audioState.audioDuration,\n                                    },\n                                  })\n                                );\n                                this.endLoad();\n                              }\n                            });\n                          }\n                          renderToContext(\n                            ctx,\n                            timelineState.left,\n                            timelineState.right,\n                            top,\n                            bottom\n                          ).then((s) => {\n                            if (\n                              initialRender &&\n                              startTimeOffset !== 0 &&\n                              endTimeOffset !== 1\n                            ) {\n                              // NOTE: If we're doing an initial render at a zoomed in portion, we need to make sure we create\n                              //  the full range image first.\n                              setInitialZoom(\n                                startTimeOffset,\n                                endTimeOffset,\n                                1,\n                                0,\n                                initialRender,\n                                true\n                              );\n                            }\n\n                            if (!!s) {\n                              this.shadowRoot.dispatchEvent(\n                                new CustomEvent(\"render\", {\n                                  bubbles: false,\n                                  composed: true,\n                                  cancelable: false,\n                                  detail: {\n                                    range: {\n                                      begin: startZeroOne,\n                                      end: endZeroOne,\n                                      min: bottom,\n                                      max: top,\n                                    },\n                                    context: userOverlayCtx,\n                                  },\n                                })\n                              );\n                            }\n                          });\n                        }\n                      );\n                    }\n                  });\n                };\n                initAudio(\n                  this.playerElements,\n                  audioFloatData,\n                  audioState\n                );\n                this.removePlaybackFrequencyBandPass();\n                if (audioState.playing || audioState.audioProgressZeroOne !== 0) {\n                  this.pause();\n                }\n                // Initial render\n                this.render({detail: {initialRender: true, force: true}});\n                // TODO: Animate to region of interest could be replaced by reactive setting of :start :end props?\n                this.resizeInited = true;\n              }\n            }\n          } catch (e) {\n            // Failed to load audio, should show an error\n            console.warn(\"aborted?\", e);\n          }\n        }\n      })();\n    }\n\n    beginLoad() {\n      if (!this.loadingSpinner.parentElement) {\n        this.timelineElements.spectrogramContainer.appendChild(\n          this.loadingSpinner\n        );\n      }\n      this.timelineElements.container.classList.add(\"loading\");\n    }\n\n    endLoad() {\n      this.timelineElements.container.classList.remove(\"loading\");\n    }\n\n    showErrorMessage(error) {\n      if (\n        this.loadingSpinner &&\n        this.loadingSpinner.parentElement\n      ) {\n        this.loadingSpinner.parentElement.removeChild(\n          this.loadingSpinner\n        );\n      }\n      this.timelineElements.container.classList.add(\"error\");\n      this.timelineElements.container.querySelector(\".error-message\").innerHTML = `Error: ${error}`;\n      this.endLoad();\n    }\n\n    init() {\n      // TODO: Maybe pass in min/max frequency bounds, so that the view is restricted to AOI?\n      // TODO: Get height dynamically from attributes, and respond to changes in height.\n      const lazyLoad = this.hasAttribute(\"lazy\");\n      const allowFullscreen = this.hasAttribute(\"allow-fullscreen\");\n      this.sharedState.displayTimeline = !this.hasAttribute(\"no-timeline\");\n      const totalHeight = this.height || 360;\n      this.timelineHeight = Math.min(60, Math.max(44, totalHeight - 200));\n      this.spectrogramHeight = this.sharedState.displayTimeline ? totalHeight - this.timelineHeight : totalHeight;\n\n      const src = this.getAttribute(\"src\");\n      const root = this.shadowRoot;\n      if (!this.inited) {\n        root.appendChild(template.content.cloneNode(true));\n      }\n\n      const container = root.getElementById(\"container\");\n      const spectrogramContainer = root.getElementById(\"spectrogram-container\");\n      const canvasContainer = root.getElementById(\"canvas-container\");\n      const miniMapContainer = root.getElementById(\"mini-map\");\n      const mapCanvas = root.getElementById(\"map-canvas\");\n      const canvas = root.getElementById(\"spectrogram-canvas\");\n      const overlayCanvas = root.getElementById(\"spectastiq-overlay-canvas\");\n      const timescaleCanvas = root.getElementById(\n        \"spectastiq-timescale-overlay-canvas\"\n      );\n      const userOverlayCanvas = root.getElementById(\"user-overlay-canvas\");\n      const playheadCanvas = root.getElementById(\"playhead-canvas\");\n      const timelineUICanvas = root.getElementById(\"timeline-ui-canvas\");\n\n      if (!this.sharedState.displayTimeline) {\n        miniMapContainer.style.display = \"none\";\n      }\n\n      this.formatTime = (time) => {\n        let seconds = Math.floor(time);\n        if (seconds < 60) {\n          return `0:${`${seconds}`.padStart(2, \"0\")}`;\n        }\n        const minutes = Math.floor(seconds / 60);\n        seconds = seconds - minutes * 60;\n        return `${minutes}:${seconds.toString().padStart(2, \"0\").padEnd(2, \"0\")}`;\n      };\n\n      const setupDefaultControls = () => {\n        const additionalControls = root.querySelector(\".additional-controls\");\n        const showAdditionalControlsBtn = root.querySelector(\".show-additional-controls\");\n        const timeElapsed = root.querySelector(\".time-elapsed\");\n        const fullscreenToggleBtn = root.querySelector(\".fullscreen-toggle\");\n        if (allowFullscreen) {\n          fullscreenToggleBtn.classList.add(\"allow-fullscreen\");\n          const initialHeight = this.height;\n          const spectastiq = this;\n          const uiHeight = 44;\n          const exitFullscreen = () => {\n            if (!document.fullscreenElement) {\n              spectastiq.height = initialHeight;\n              window.removeEventListener(\"fullscreenchange\", exitFullscreen);\n            }\n          };\n          fullscreenToggleBtn.addEventListener(\"click\", () => {\n            if (!document.fullscreenElement) {\n              document.documentElement.requestFullscreen().catch(err => {\n                console.log(`Error attempting to enable fullscreen: ${err.message}`);\n              }).then(() => {\n                window.addEventListener(\"fullscreenchange\", exitFullscreen);\n                spectastiq.height = window.screen.height - uiHeight;\n              });\n            } else {\n\n              document.exitFullscreen().then(exitFullscreen);\n            }\n          });\n        }\n        root.addEventListener(\"playhead-update\", e => {\n          timeElapsed.textContent = `${this.formatTime(e.detail.timeInSeconds)} / ${this.formatTime(this.timeline.timelineState.audioState.audioDuration)}`;\n        });\n\n        let showingAdditionalControls = false;\n        const hideAdditionalControls = () => {\n          showingAdditionalControls = false;\n          additionalControls.classList.remove(\"show\");\n        };\n\n        // Handle clicks outside the web component\n        document.addEventListener(\"click\", (e) => {\n          if (showingAdditionalControls && e.target !== this.shadowRoot.host) {\n            hideAdditionalControls();\n          }\n        });\n\n        this.shadowRoot.addEventListener(\"click\", (e) => {\n          // Handle clicks inside the web component that aren't the menu\n          if (showingAdditionalControls &&\n            !additionalControls.contains(e.target) &&\n            !showAdditionalControlsBtn.contains(e.target)) {\n            hideAdditionalControls();\n          }\n        });\n\n        showAdditionalControlsBtn.addEventListener(\"click\", () => {\n          showingAdditionalControls = !showingAdditionalControls;\n          additionalControls.classList.toggle(\"show\");\n        });\n\n        const freqSwitch = root.getElementById(\"freq-switch\");\n        if (this.frequencyScale) {\n          freqSwitch.setAttribute(\"checked\", \"checked\");\n        }\n        // TODO: If frequencyScale is changed programmatically, we might want to sync the state here.\n        freqSwitch.addEventListener(\"change\", () => {\n          this.frequencyScale = freqSwitch.checked.toString();\n        });\n        const timeSwitch = root.getElementById(\"time-switch\");\n        if (this.timeScale) {\n          timeSwitch.setAttribute(\"checked\", \"checked\");\n        }\n        timeSwitch.addEventListener(\"change\", () => {\n          this.timeScale = timeSwitch.checked.toString();\n        });\n        const palettes = root.querySelector(\".palettes\");\n        const svgTick = palettes.querySelector('.svg-tick');\n        for (const [color, gradient] of Object.entries(COLOR_MAPS)) {\n          const button = document.createElement(\"input\");\n          button.type = \"radio\";\n          button.name = \"palette-group\";\n          button.id = `palette-${color}`;\n          button.classList.add(\"radio-custom\");\n\n          const label = document.createElement(\"label\");\n          label.setAttribute(\"for\", button.id);\n          label.classList.add(\"radio-custom-label\");\n          const icon = document.createElement(\"canvas\");\n          icon.width = 16 * devicePixelRatio;\n          icon.height = 16 * devicePixelRatio;\n          label.appendChild(icon);\n          const labelText = document.createElement(\"span\");\n          labelText.textContent = color;\n          label.appendChild(labelText);\n          label.appendChild(svgTick.cloneNode(true));\n          palettes.appendChild(button);\n          palettes.appendChild(label);\n          if (this.colorScheme.toLowerCase() === color) {\n            button.setAttribute(\"checked\", \"checked\");\n          }\n          const ctx = icon.getContext(\"2d\");\n          for (let y = 0; y < icon.height; y++) {\n            const slot = (y / icon.height) * gradient.length;\n            const color = gradient[Math.floor(slot)];\n            ctx.fillStyle = `rgb(${color[0] * 255}, ${color[1] * 255}, ${color[2] * 255})`;\n            ctx.fillRect(0, y, icon.width, 1);\n          }\n          button.addEventListener(\"change\", () => {\n            this.colorScheme = color;\n          });\n        }\n        // Init palette options.\n      };\n\n      // FIXME: Maybe only do this if there are no user-supplied controls?\n      setupDefaultControls();\n\n      if (!(src && !this.inited)) {\n        container.classList.add(\"no-src\");\n        // NOTE: No audio src, show ability to load from disk.\n        const selectUserFileBtn = root.querySelector(\".select-user-file-btn\");\n        const fileInput = root.querySelector(\".select-user-file-input\");\n        selectUserFileBtn.addEventListener(\"click\", () => {\n          fileInput.click();\n        });\n        fileInput.addEventListener('change', (e) => {\n          if (e.target.files && e.target.files.length !== 0) {\n            root.getElementById(\"container\").classList.remove(\"no-src\");\n            const file = e.target.files[0];\n            this.localSrc = e.target.files[0].name;\n            this.loadSrc(URL.createObjectURL(file));\n          }\n        });\n      }\n\n      if (!this.loadingSpinner) {\n        this.loadingSpinner = root.getElementById(\"loading-spinner\");\n        this.loadingSpinner.parentElement.removeChild(this.loadingSpinner);\n      }\n      if (!this.progressBar) {\n        this.progressBar = root.getElementById(\"progress-bar\");\n        this.progressBar.parentElement.removeChild(this.progressBar);\n      }\n      const mainPlayheadCanvas = root.getElementById(\"main-playhead-canvas\");\n      const controls = root.getElementById(\"controls\");\n      const playButton = root.getElementById(\"play-button\");\n\n      this.timelineElements = {\n        mapCanvas,\n        canvas,\n        canvasContainer,\n        miniMapContainer,\n        userOverlayCanvas,\n        overlayCanvas,\n        timescaleCanvas,\n        timelineUICanvas,\n        mainPlayheadCanvas,\n        container,\n        spectrogramContainer,\n      };\n\n      this.playerElements = {\n        playheadCanvas,\n        mainPlayheadCanvas,\n        playButton,\n        canvas,\n        overlayCanvas,\n        playheadCanvasCtx: playheadCanvas.getContext(\"2d\"),\n        mainPlayheadCanvasCtx: mainPlayheadCanvas.getContext(\"2d\"),\n      };\n      const resizeCanvas = (canvas, width, height, forReal = true) => {\n        canvas.style.height = `${height}px`;\n        canvas.style.width = `${width}px`;\n        // NOTE: Defer resizing the backing canvas until we actually want to draw to it, this makes resizes look better.\n        if (!this.resizeInited || forReal) {\n          canvas.height = height * devicePixelRatio;\n          canvas.width = width * devicePixelRatio;\n        } else {\n          this.deferredWidth = width;\n          this.deferredHeight = height;\n        }\n      };\n      overlayCanvas.addEventListener(\"interaction-begin\", () => {\n        controls.classList.add(\"disabled\");\n        this.sharedState.interacting = true;\n\n        // TODO: Await requestAnimationFrame?\n        // TODO: Set timeout in case we're interacting but not moving?\n        if (this.sharedState.displayTimeline) {\n          const startZeroOne = timelineState.left;\n          const endZeroOne = timelineState.right;\n          drawTimelineUI(startZeroOne, endZeroOne, timelineState.currentAction);\n        }\n      });\n      overlayCanvas.addEventListener(\"interaction-target-changed\", () => {\n        // Canvas-based hit areas changed, so redraw handles for hover states etc.\n        if (this.sharedState.displayTimeline) {\n          const startZeroOne = timelineState.left;\n          const endZeroOne = timelineState.right;\n          drawTimelineUI(startZeroOne, endZeroOne, timelineState.currentAction);\n        }\n        updatePlayhead();\n      });\n      overlayCanvas.addEventListener(\"interaction-end\", () => {\n        controls.classList.remove(\"disabled\");\n        this.sharedState.interacting = false;\n        this.render &&\n        this.render({detail: {initialRender: false, force: true}});\n      });\n\n      const clearOverlay = () => {\n        const overlayCtx = overlayCanvas.getContext(\"2d\");\n        overlayCtx.clearRect(\n          0,\n          0,\n          overlayCtx.canvas.width,\n          overlayCtx.canvas.height\n        );\n      };\n      this.clearOverlay = clearOverlay;\n\n      const redrawFrequencyScaleOverlay = (state, actualSampleRate) => {\n        const ctx = overlayCanvas.getContext(\"2d\");\n        ctx.save();\n        const isDarkTheme = state.isDarkTheme;\n        state.textMeasurementCache = state.textMeasurementCache || {};\n        const maxFreq = actualSampleRate / 2;\n        const pixelRatio = window.devicePixelRatio;\n        ctx.font = `${10 * pixelRatio}px sans-serif`;\n        const divisions = Math.ceil(maxFreq / 1000) + 1;\n        const divisionColor = isDarkTheme\n          ? \"rgba(255, 255, 255, 0.1)\"\n          : \"rgba(0, 0, 0, 0.1)\";\n        const textColor = isDarkTheme\n          ? \"rgba(255, 255, 255, 0.5)\"\n          : \"rgba(0, 0, 0, 0.85)\";\n        //start at bottom?\n        ctx.strokeStyle = divisionColor;\n        ctx.lineWidth = Math.max(1, devicePixelRatio / 2);\n        ctx.textAlign = \"right\";\n        ctx.textBaseline = \"bottom\";\n        ctx.fillStyle = textColor;\n        const label = \"kHz\";\n        const labelX = ctx.canvas.width - 4 * devicePixelRatio;\n        const kHzLabelY = ctx.canvas.height - 4 * devicePixelRatio;\n        ctx.fillText(label, labelX, kHzLabelY);\n        const cacheKey = `${label}_${pixelRatio}`;\n        const textHeight =\n          state.textMeasurementCache[cacheKey] ||\n          ctx.measureText(label).actualBoundingBoxAscent;\n        state.textMeasurementCache[cacheKey] = textHeight;\n\n        let prevY = (kHzLabelY - textHeight * 0.5) / pixelRatio;\n        const minYOffsetFromMargin = Math.abs(\n          prevY - ctx.canvas.height / pixelRatio\n        );\n        for (let i = divisions; i >= 0; i--) {\n          const yy = i / divisions;\n          const yyy = this.transformY(1 - yy);\n          let y = ((1 - yyy) * ctx.canvas.height) / pixelRatio;\n          if (prevY - y > 15) {\n            ctx.strokeRect(\n              0,\n              y * pixelRatio,\n              ctx.canvas.width - 25 * pixelRatio,\n              0\n            );\n            ctx.textBaseline = \"middle\";\n            const thisFrequency = Math.round((1 - yy) * maxFreq) / 1000;\n            const label = `${thisFrequency.toFixed(1).toLocaleString()}`;\n            // NOTE: Measure text takes a fair bit on time on chrome on lower powered android devices; cache measurements.\n            const cacheKey = `${label}_${pixelRatio}`;\n            const textHeight =\n              state.textMeasurementCache[cacheKey] ||\n              ctx.measureText(label).actualBoundingBoxAscent;\n            state.textMeasurementCache[cacheKey] = textHeight;\n            const overshootBottom = y + (textHeight + 5 * pixelRatio) * 0.5;\n            const overshotBottom =\n              overshootBottom >= ctx.canvas.height / pixelRatio;\n            if (overshotBottom) {\n              ctx.textBaseline = \"bottom\";\n              y -= 2 * pixelRatio;\n            }\n            // NOTE: fillText is also slow on low-end devices, consider caching text and blitting.\n            if (y * pixelRatio > 0) {\n              ctx.fillText(\n                label,\n                ctx.canvas.width - 4 * pixelRatio,\n                Math.max(minYOffsetFromMargin * pixelRatio, y * pixelRatio)\n              );\n              prevY = y;\n            }\n          }\n        }\n        ctx.restore();\n      };\n\n      overlayCanvas.addEventListener(\"range-change\", (e) => {\n        this.render && this.render(e);\n      });\n      let prevMax = 0;\n      let prevMin = 1;\n      this.shadowRoot.addEventListener(\"render\", (e) => {\n        const yRangeChanged =\n          e.detail.range.min !== prevMin || e.detail.range.max !== prevMax;\n        prevMax = e.detail.range.max;\n        prevMin = e.detail.range.min;\n        if (yRangeChanged) {\n          this.drawFrequencyScale && clearOverlay();\n          this.drawFrequencyScale &&\n          redrawFrequencyScaleOverlay(timelineState, this.actualSampleRate);\n        }\n      });\n      this.shadowRoot.addEventListener(\"audio-loaded\", (e) => {\n        container.classList.add(\"audio-loaded\");\n        this.drawFrequencyScale && clearOverlay();\n        this.drawFrequencyScale &&\n        redrawFrequencyScaleOverlay(timelineState, e.detail.sampleRate);\n      });\n      overlayCanvas.addEventListener(\"double-click\", async (e) => {\n        if (this.applicationHandlesDoubleClick) {\n          this.shadowRoot.dispatchEvent(new CustomEvent(\"double-click\", {\n            detail: e.detail,\n            composed: true,\n            bubbles: false,\n            cancelable: false,\n          }));\n        } else {\n          // If there's a selected track, should we stop at the end of it?\n          await play(e.detail.audioOffsetZeroOne);\n        }\n      });\n\n      const timeline = initTimeline(\n        this.shadowRoot,\n        this.sharedState,\n        this.timelineElements\n      );\n      const {\n        drawTimelineUI,\n        timelineState,\n        setInitialZoom,\n        animateToRange,\n        getMaxXZoom,\n        getMaxYZoom,\n        resetYZoom,\n      } = timeline;\n      this.timeline = {\n        drawTimelineUI,\n        redrawFrequencyScaleOverlay,\n        timelineState,\n        setInitialZoom,\n        animateToRange,\n        getMaxXZoom,\n        getMaxYZoom,\n        resetYZoom,\n      };\n\n      const player = initAudioPlayer(\n        this.shadowRoot,\n        this.sharedState,\n        this.timeline.timelineState,\n        this.playerElements\n      );\n      const {\n        audioState,\n        updatePlayhead,\n        setPlaybackOffset,\n        setGain,\n        setBandPass,\n        startPlayheadDrag,\n        endPlayheadDrag,\n        dragLocalPlayhead,\n        dragGlobalPlayhead,\n        removeBandPass,\n        play,\n        pause,\n      } = player;\n\n      // TODO: Probably move audio progress to sharedState\n      timelineState.audioState = player.audioState;\n      timelineState.startPlayheadDrag = startPlayheadDrag;\n      timelineState.endPlayheadDrag = endPlayheadDrag;\n      timelineState.dragLocalPlayhead = dragLocalPlayhead;\n      if (this.sharedState.displayTimeline) {\n        timelineState.dragGlobalPlayhead = dragGlobalPlayhead;\n      }\n      this.audioPlayer = {\n        audioState,\n        updatePlayhead,\n        setPlaybackOffset,\n        setBandPass,\n        removeBandPass,\n        startPlayheadDrag,\n        endPlayheadDrag,\n      };\n\n      this.play = play;\n      this.pause = pause;\n      this.setGain = setGain;\n\n      this.animateToRegionOfInterest = async (start, end, min, max) => {\n        const initialStart = timelineState.left;\n        const initialEnd = timelineState.right;\n        const initialTop = timelineState.top;\n        const initialBottom = timelineState.bottom;\n        await animateToRange(\n          initialStart,\n          initialEnd,\n          initialTop,\n          initialBottom,\n          start,\n          end,\n          max,\n          min,\n          200,\n          (left, right, top, bottom, final) => {\n            setInitialZoom(left, right, top, bottom, false, final);\n          }\n        );\n      };\n\n      this.setPlaybackFrequencyBandPass = (minFreq, maxFreq) => {\n        setBandPass(minFreq, maxFreq);\n      };\n      this.removePlaybackFrequencyBandPass = () => removeBandPass();\n\n      this.selectRegionOfInterest = async (start, end, min, max) => {\n        {\n          const centerX = start + (end - start) * 0.5;\n          if (audioState.audioContext.state !== \"running\") {\n            if (navigator.audioSession) {\n              // Try to work around issue where iOS won't play audio if phone is muted.\n              // (iOS 17+)\n              navigator.audioSession.type = 'playback';\n            }\n            // If we start zooming to a region before the context is running, things break.\n            await audioState.audioContext.resume();\n          }\n          const maxXZoom = getMaxXZoom();\n          const pRange = (end - start) * 1.1;\n          const paddedRange = Math.max(1 / maxXZoom, pRange);\n          const range = paddedRange / 2;\n          start = Math.max(centerX - range, 0);\n          if (centerX - range < 0) {\n            end = centerX + range + Math.abs(centerX - range);\n          } else {\n            end = centerX + range;\n          }\n          start = Math.max(0, start);\n          end = Math.min(1, end);\n        }\n        {\n          const maxYZoom = getMaxYZoom();\n          // Make sure max - min is at least 1/maxYZoom;\n          const minZoomYHeight = 1 / maxYZoom;\n          let range = max - min;\n          if (range < minZoomYHeight) {\n            const centerY = min + range * 0.5;\n            min = centerY - minZoomYHeight * 0.5;\n            max = centerY + minZoomYHeight * 0.5;\n            min = Math.max(0, min);\n            max = Math.min(1, max);\n            range = max - min;\n            if (min === 0 && range < minZoomYHeight) {\n              max = minZoomYHeight;\n            } else if (max === 1 && range < minZoomYHeight) {\n              min = 1 - minZoomYHeight;\n            }\n          }\n        }\n\n        this.timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-begin\")\n        );\n        await this.animateToRegionOfInterest(start, end, min, max);\n        this.timelineElements.overlayCanvas.dispatchEvent(\n          new Event(\"interaction-end\")\n        );\n      };\n      this.resetYZoom = resetYZoom;\n      this.redrawFrequencyScaleOverlay = () =>\n        redrawFrequencyScaleOverlay(timelineState, this.actualSampleRate);\n\n      this.enterCustomInteractionMode = () => {\n        timelineState.customInteractionMode = true;\n        this.timelineElements.spectrogramContainer.classList.add(\n          \"custom-interaction-mode\"\n        );\n      };\n      this.exitCustomInteractionMode = () => {\n        timelineState.customInteractionMode = false;\n        this.timelineElements.spectrogramContainer.classList.remove(\n          \"custom-interaction-mode\"\n        );\n      };\n      this.beginCustomInteraction = () => {\n        timelineState.inCustomInteraction = true;\n      };\n      this.endCustomInteraction = () => {\n        timelineState.inCustomInteraction = false;\n      };\n\n      this.resizeCanvases = (resizedWidth, forReal) => {\n        const width = resizedWidth || container.getBoundingClientRect().width;\n        this.timelineElements.canvasContainer.style.height = `${this.spectrogramHeight}px`;\n        this.timelineElements.miniMapContainer.style.height = `${this.timelineHeight}px`;\n        const initialWidth = this.timelineElements.canvas.width;\n        resizeCanvas(\n          this.timelineElements.canvas,\n          width,\n          this.spectrogramHeight,\n          forReal\n        );\n        resizeCanvas(\n          this.timelineElements.timescaleCanvas,\n          width,\n          Math.max(20, Math.min(30, this.spectrogramHeight / 10)),\n          true\n        );\n        resizeCanvas(\n          this.timelineElements.overlayCanvas,\n          width,\n          this.spectrogramHeight,\n          true\n        );\n        resizeCanvas(\n          this.timelineElements.userOverlayCanvas,\n          width,\n          this.spectrogramHeight,\n          true\n        );\n\n        if (this.sharedState.displayTimeline) {\n          resizeCanvas(\n            this.timelineElements.mapCanvas,\n            width,\n            this.timelineHeight,\n            forReal\n          );\n          resizeCanvas(\n            this.timelineElements.timelineUICanvas,\n            width,\n            this.timelineHeight,\n            true\n          );\n          resizeCanvas(\n            this.playerElements.playheadCanvas,\n            width,\n            this.timelineHeight,\n            true\n          );\n        }\n        resizeCanvas(\n          this.playerElements.mainPlayheadCanvas,\n          width,\n          this.spectrogramHeight,\n          true\n        );\n        let didChangeWidth = false;\n        width * devicePixelRatio !== this.timelineElements.canvas.width;\n        if (forReal) {\n          didChangeWidth =\n            this.deferredWidth &&\n            this.timelineElements.canvas.width ===\n            this.deferredWidth * devicePixelRatio &&\n            this.deferredWidth !== initialWidth;\n          this.deferredWidth = undefined;\n          this.deferredHeight = undefined;\n        }\n\n        const wasTriggeredByResizeEvent = !!resizedWidth;\n        if (wasTriggeredByResizeEvent && !!this.resizeInited) {\n          {\n            if (this.sharedState.displayTimeline) {\n              const startZeroOne = timelineState.left;\n              const endZeroOne = timelineState.right;\n              drawTimelineUI(startZeroOne, endZeroOne, timelineState.currentAction);\n            }\n            if (this.actualSampleRate) {\n              this.drawFrequencyScale && clearOverlay();\n              this.drawFrequencyScale &&\n              redrawFrequencyScaleOverlay(timelineState, this.actualSampleRate);\n            }\n            if (!audioState.playing) {\n              audioState.progressSampleTime = performance.now();\n              updatePlayhead();\n            }\n          }\n          this.sharedState.interacting = true;\n          clearTimeout(this.sharedState.interactionTimeout);\n          this.timelineElements.container.classList.add(\"disabled\");\n          this.sharedState.interactionTimeout = setTimeout(() => {\n            // TODO: We only want to invalidate the caches if the *backing* size of the spectrogram has changed,\n            //  and it needed to be re-rendered.  Otherwise just draw what we have again with no delay.\n            //  Worth noting though that our webgl canvases *do* need to be resized at some point, and at that point\n            //  we need to re-upload the textures, which can cause a hitch\n            if (didChangeWidth) {\n              this.invalidateCanvasCaches && this.invalidateCanvasCaches();\n              this.renderRange &&\n              this.renderRange(0, 1, canvas.width, true).then(() => {\n                this.render({detail: {initialRender: true, force: true}});\n              });\n            } else {\n              this.render({detail: {initialRender: true, force: true}});\n            }\n            this.sharedState.interacting = false;\n          }, 300);\n        }\n      };\n      const resizeObserver = new ResizeObserver((entries) => {\n        // We'll defer resizing the spectrogram backing canvas until a new spectrogram has been created at the new\n        // width and is ready to render.\n        this.resizeCanvases(entries[0].contentRect.width, false);\n      });\n      resizeObserver.observe(container);\n      if (lazyLoad) {\n        const intersectionObserver = new IntersectionObserver((intersection) => {\n          if (intersection[0].isIntersecting && src && src !== \"null\" && src !== \"undefined\") {\n            this.loadSrc(src);\n            intersectionObserver.disconnect();\n          }\n        }, {\n          rootMargin: '50px',\n          threshold: 0.1\n        });\n        intersectionObserver.observe(container);\n      } else {\n        // Initial attributeChangedCallback happens before connectedCallback, so need to load src after initial one-time setup.\n        if (src && src !== \"null\" && src !== \"undefined\") {\n          // NOTE: Vue initially passes `null` or `undefined` to src, which gets stringified.\n          this.loadSrc(src);\n        }\n      }\n      this.inited = true;\n    }\n  }\n  if (!customElements.get(\"spectastiq-viewer\")) {\n    customElements.define(\"spectastiq-viewer\", Spectastiq);\n  }\n\n  return Spectastiq;\n\n})();\n"]}