From 757d4e5eee056f3082ba9d91882a57c5583fe485 Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 20:02:29 +0800
Subject: [PATCH 1/7] first prototype of MediaQuickview

---
 bun.lockb                                   | Bin 301673 -> 301705 bytes
 package.json                                |   1 +
 src/platform/MediaQuickview.css             |  69 ++++++++
 src/platform/MediaQuickview.tsx             | 179 ++++++++++++++++++++
 src/timelines/toots/MediaAttachmentGrid.tsx |  43 ++---
 5 files changed, 261 insertions(+), 31 deletions(-)
 create mode 100644 src/platform/MediaQuickview.css
 create mode 100644 src/platform/MediaQuickview.tsx

diff --git a/bun.lockb b/bun.lockb
index 0645c92b80358a366bc2a50d54c803ad3054f6c8..bec25b12c78c96f732f0b82a831c37f2ac0aa4af 100755
GIT binary patch
delta 23277
zcmeHPcYGAp_Megsgdz|Uk`P)H1jH=abO<CwDS|-21_?EU0D*)S5NRe91w}v@cvrws
z#0H9ph>P$ni2B4Q(ySB(tPxO9@u^Sn_x;YENj4Z!pZeF2`RsST=bn4+xqar&-PtXD
zI{2<F!T0vme^`{!rf8v|X~CLS{FCr3a1De{fop+p1=j>$8=z^Ifmea6ftL!$gK2j;
zn0`h|`ob~!c|)T|7ETTH#TVv|A9HJVzLry1IAQYGTrClL<Wp3fT~*V<k+3s365JBp
z5F7+<0zOqm(;9%^0!M(K1=DU7xITC;I1D@;Tpf%Iq9=^c&AkQQS@43PqoaprPsqN?
zmtk=xl=YzB39hCUX~nsPIobKyLvy=B!34=Tg@CUHvn3*>2DQY&K~$GD-3q=OTmoi+
zG_f0=KW0dFexWurcgV!ynx+*L;)G*p9bZc%g7GPy4=1&YAbbcR2%Iw_JAV`m3+nm`
zFbK>hACX-cJuE+a_)Q42hVxK57W5#PUC{^L814eD4Sqe;*HT?kO%^N`%of@VJ-HiB
z5Q|3^<`%kgkl|VDE~9bYRP+MtRRCtTJ;9B^S!iXtNd>bbqM?rhZ-<h_o}8VZpI10R
zD?zvs!t=oN6An9O<BWv<7#Z}t->PGVrT<I!b>9m}!c-69#9S7D**%lMEUXh>7xqmM
z?k{%RtUNQx<MhdeeX#-~h-Kf?%$G}sjN(q#K69g<WX%X@9qWdU<sXq<P>?%*WMKn@
zS#AuL+~{GW3xg46^A5?MI38UvBEYbY2lNa&je>A^9NXN+HAvSBtYv{2`ckVjFin4b
zbIU4WroMD@dbK1o>NmS@^Rk*5E<M#M4ep}PvOIMgM>vztnT+2J^bMJXR$kpnLC%QY
zxw*7%lCDqR92HtcZ|Mv>XLvcIp~5gjdAn@xT`w$5Ut{G(rx}--*2w5oSC*-19jt=r
z6xVV{oo(qTq-(4V-P>8~+-dqcs}$!?td_0Q^e8J2=MmOAoS(KzalXcC8Iz{pZRN$J
zx%OiCCt3wDDMnxw>x-CFy`9z4ljfR?X`E?Ads1BcAq}!651Kv4mX<)uv!!#8vTbP~
z8oi$_JqyW~A{^s>fUTPdsShRnsI@LGEvONuXfmp2SZ9_eTP@?$^oOiGoIkME;XKAF
z#rYkpWkQ-O7=tg_+Tc#nyIJcJ()9UODb7c%mWgS4Q!5YW9BW--n(HLyb9bv?a*7Ur
zNoo2@D-Y-ItaUgSTBS*8L62dg#UjrD>&!FBRS~$_F2E*tk`WYatxryMb;UsHXhkQb
zxJn>hLn-KUNNw#@<0dERms{)Fr0FxPQk?f$E!(CAg<@2(CZ@&s9@e_HY5ILuDPlfG
zOgqHr)|r?jR{|zgJ8J`ckAsA+N>0%~vPx6Zf`($^++f>z+9tW4L8u$+YiMEC`gW;7
zgD{^u+tHiaCh1RDE!(HLjzWh<NlXcP1k*Jhv4Pf^xFqy=Y5O!IEW)}fHPv+!uDf=N
zxK=|-Wsa^7APr_h*F;<w`r38f4Jpfte!N`(X7W(m>&uW>X_qxFCMoDVLWu~Wil|6D
zt|kMm0#sx%q|Ubedz4^dSmUCTT#Yb|ldV&2Q}kP`ymXYO(dPB(Kb91sz1ij(hN{*Y
zz?9Tt03Kw9JpkiTCk->1`Pzp+(=G`hqv<t_P^|;Nbk_pZcLaF&aS^WzT2EX3*D?#*
zOZ@sXZm!x*01MU!H!b_<<0B}3JIsVxVpkdC=BZ^%Jef5gB4IMq=K?I?aOTev@n*$?
zOoP!3;2_h%7zS{V86GP<4$Om0{dj=>ZUvaZZ2%8{rvIt77K;W)AY-;_cT2>@n2zrO
zT)-lLVM}-^nEK@a3$y~@Q5hp$kye68mS{D=LGviUG4T{Y2b%%LKLaqMt-{;DjNdNd
z=fKSPB?-SQ{HpM4VBFfZw*jVm2MA#QAA~^1hXFb`Cj2Rw2bl#ZWdMgiGu;VWTRGF;
zX@GWLiyfH-_=aKjKZP2=&j1fH6X>uao5C(I69(eM0tHF98klx9gfA1WEgTG{eW-9a
z7#}SHry?edgg|ZxrePB>K3Wt{e-U$5v=INTz|1dNNBx<H8z&khh(R)yIQ*IMX`(07
zK{}YKYjEPI?=1T3z^usi67C_K0met`CE*)%v_BO$O2kbJ;2`6Upyf%JOo#bkT;{b|
zqW{xe^jA6h1Ab^a8z)w%XbuD_=JF&=@h%DbGgWuvM4l({WOl`T3I7pBx*~0%BqUS3
zS9p==$($V}qF)JShN~r>OmU5bD`VQPMLehDqhjY{^nXP~#V~ExNdhvP>~Sz1J|X<1
z#FHs*lJLct7rJL@PhlGqSka#~*EOrhXN~P4@uD~(Gqab#%={J6`!iLq;zZ`N01t9K
z@DU0B0Y;PjZwddRbiVxUl5<WUlNwx%o53y+&BT_f%F_i*RW+Of!L`8*^O=MPnOW2o
z4&fz{icl~M5CO(VixfQ{Zg`Lx-$e9LqW?Y2u52py7jjK1Sb%0=)4HRyxjiDT5_5lM
zePTrK&rI!so>6fUUl}thLE_0wmk4IH+KL`rWIJjnii<H5b`ZOZG3`3SjuX2VnBkiw
z9hnm~Q^J{sjEHiD&tM=7*aAbrEP1Z*FyY}~HZ=E9n89c;4>H5!C4Qpl$+W)>On;NX
zZtx>DAh1M_Gs22K5oUMDMud2f>29-FZKV~$&kAo7-VVk`drrd7Gk}B4YP>A^S0wyz
zFmv4tX0Z+kzrzlp;(aKX@ezqQ3dTn}F8WUyz(J<n7ho3bgz!m;CsWTyW~#mtJ_BYx
z--4O`dkO#7xYn28CsF(?Y`~fcT*6hsyuj54!$b=PGo!lT0Bib5Upv;9*ai~Y5X^&2
z>qf$jMeoln))ml`n}eB4OR@83>RXAPOutu~vigXY2r|VOoaiuK^#06MNQ&smbkt75
zl`;Kvka&M)$96I?u<4+)7?AmB!W{(aSAbda5->-|S}+fPrs`pwn9+LSC&Z3Sag*q`
zfazyDmE-^jhSluL=2piqxqqf<FF?brUj)-?r|_#_7UebJ*M;8@eiMw3wok$bgx?l^
z7mSZ~5GVRSB>E#@w#9LF1~d2=3XYkRV(=xH3BCfej^BXs(Z0ip_TP*CCkdYi<D=;?
zX1obzI1tQ?s*Ap+=xa+jH~{shVW=p=gd@bTfpB9m9W@2xqqPuyD=;&RmT-)OV}-f9
zOaDm{ZUe?gYcKi^MIvx}n2tJ0_&N!9lW-3SXGr)43Ew2)J`&E9aDOm|Z4MY8Z8%Q!
zp9f}v@+Dj}T7<D+e6$I|w}ELm70j9!gXv(V@N9{nBjLM*=LydjUMRc>%r0FFreE%V
z<5Q$9!<mj(2$z6quo{ey_8?Ec!nAt?@z~SSo&qzx1sn|im!!KmqyH~%@aN)LhJWKk
z-XjI_=OE6%_YlFRd0z~VNcaOuKxPXa1#>2UDtdor{O8cqPbrvP_N9cs5<U&4-Pd4_
zjUT|7ycxS-&8Djgra?8P7H$S^Enza_Jz!=OD;x)A!P<c7ryba^O1=(@qE$LV$Vux&
zEw~Gq`s-|~BVUKv*P!b~+a1g@^##*cKQNDrG4swA`=Mg*&-61KdR}72fa!lKn5CHJ
z!Wd-nilM+qy8|aCn9G1L4eydLnc=&|Za$dRS}5`UO#LF!`!oG75&aTZU7y3Hq96xY
z4ZroR0GEsQk1(sV66sjXRg#{}Y#$K)S}^S%k@!m3?ibsD3D=7une}>H_z5sG+9>h<
z%ydtQp3HPlOZZ|;e_LG0pTaYez@IsMpM#z~xeLsU-T+g7K*H~Unf_fcGkzb;8Xplp
z2IfJ==DGF-7@M-1iQ&X_0Ywm)Fi;d#gsY0ans5y;ebtfpV9|$wnPE7X1>_fDbj;&w
z0rkJO16%YrZo&SZ9oVA(X^WOE=D+odpdDsQlyAXO%dJ@7Q8}ak{Ug{^m2JJ!1@-Lv
zYzA<U+0;WAz(HpETz~}}4zNHM-GSx!|GTzex%0|{{A)X~_WbkTl4Z&70XW$H<QDAj
z*@5*vaQr=6u)k*q7WHSJ`ER`<XouMne`*UB>3?$vmJfW)=<nEqrQhG$fi1FcpD_G0
z%vsR_6=6&IZNb{J<2QC-i|nmfI!NPO5@yNM5vJ;Y--7j}zxWO;`ae%f{+DmTN`)@E
zmCN~eQG-7i&pe$i*x$1QYtMgf#WLYvyaoGvc3_L_+w0%61zT~)74@sIBg+b}({rfy
zZN+l*{-s;6@ONnk7L8E8MQo2YpMrPBW9)<9;AV)wv;%85lf5I$CcCr)dua#OHzF?W
zz}l;)OFOWac3>~<z+T#c{k4r$tUE64z+T#c#Xa-V4y?U!@Y{*?t;wn21JM7!9oSy7
zllqtKz<S%%&}aRprt@2eH}J(#h*}eDTpQ~Aydft)&pooxxABwdjS4WT7<v<>CmYvW
zG0`zV4Qp_8fa;ZORMj6+b*?lb?eO71qiV$RaNiFJxJ5FuaLnig@FD1`dDny)pBdtS
zPW}!w_4d8Y*czrk<VC4YT&6Ggo{Kk3(^vXFK6QrfeYA}ca#v^zd#GvJB$SoK<(A^7
zYTrvn-S(eR#xVtYKGARk=yTCc6&=4x*(SPaqT?R=b``Y~F3#c!l1XnzRBbT7pZkta
zl{glNIk)beufi5W$87kj=3X_9E)IzKeTc%x-X#4<o=KOB3%dS9bSuQJ4s@TX9dvOE
zqBw6>A&Rd>;d_>{O~WnXA_TfM0FP%x$9DkRs5&phMO%n0i1Qi;Uju9p@Niz~KnH87
zw#N5b2MfgS5#m%fUF?#m7sY!d!u)2AO}GO(eE72vjo;F-33rMPuWoCj)h4=_29eG6
zszf!0JY96JLB|}M0Jj4?c8M+ua*@(sfs115al9ceu7ErnpxZY^*Az0pJG6U2bj_eU
zp?cGW^L6A~64e}0{L1n&FkVlwOTl4v5IP?3id{>{htwLnaBlR6#Jm-vy0MFJ92VWx
zkb8=bFS)RC(LjzmLl=1x^`S)Z%af6!J1V*u=n6%5OmzG%Wr|9F6)r4^`dFgk5Vb^f
zpNK9Vx^(C`#P}e`Xin#7wTLd9*ZiEfXp#`+Wl3@HjT(x{fD+x8qH6=){puK9aQm6Z
zX^BdK{H?9gG`@txl<k1CqWfBO9Dd&^&%fZ}KN7`vb!eUjd@s7QqU(V052E`{bm<8H
zsAkZ`e<g~q`Ow9+z)zz4L3AAvJ}0^#Mb`=8EVYj=`a$HBIVVwFAm0e^I4`>EAWz2i
zf`ghb4l%2)z%Ui{8eGhW$U{dnBUZZ}SO`P`CUm^gbO-ol$mIavF```$V4fOB7yNRY
zgSCc4Wk5a--Id_WMAr+l1#px)uP60}yhQDwi<L0vh^!|$vJwh99+BdL-(c|-298p`
ztHhGs49r$_cEJVTmf&E$LZbNn)$QV<1$3-;Utpx@__`7+k_ik@*>o{TqWD4*&Dp$z
zMduM+e<p#BgFhBJe6#^TXSIngQV_*CkOVyoI0)byESv*;Ux~WGz+;eO!5ru;MK*Ay
z(qD%QzO}`pBbcSg0i3<?PNEwMT?iv_@GT~axj>leO&6v_T_;h)5j70x1auYM2*?Ln
z92~q5Fi9S;NUfoZ#Z&`55QXyFU)S-gK8}yx(6P|@0NaSV8^uKd<bmo8U9gZGCO3=u
z7|3+Ne(xi?v5@J4x=e8~4sw=C|2JGP>IR^{L={5D56f)b0C6z^VZH{%F*8VX6Cv+c
zi|B$ma_Hnr)FjAsOWknjSnt~ax}|P}*iD9<rH(nJxCOy{F`o*V=F}C4Id2qvfrrCx
z40LSS>A*zgc>|^R1S*ccNfO27<>!!jOarqN#Xza(ri0nUGk`2LgDzNT4%QiBJ_|BS
zLETI-pADIBZn4s{!7Rlczzb?0T?~Q90X$F4=RjsJQ8!<7b0PB;C=Tj{qPq)nR~5Be
znv=OMm8f};Su*Mrbgald5~l7xv6~M$OO2xonsZ*<FXp`5(3H9o(cKF<1Tss!Qd}&8
z9IAHE#Rg`BV~v>e(sHNh9uVCU=tj{9$6C=Xg`B18yeXv^i6CF*V@d^Zj_?;ikBII*
z(Q)KHD!OIVscgDnD|4i+M-)421;ENt_qdqf51DRn1vZGT1oBq3i7pt$k+xByRzap)
z4yPx@?P|z$OWjjqw+8Y+rSE|YzAna*#=S0<Vl6OPbY9Ut2;CIXJqsN^+CxB=>P;8)
zemn3y^lXbq0QLxVd_$3L9|ibwAV=;F(XE4gQmvtjQ&1Hn_zD;qX^#P{+zenBbaeYT
zV2@1j>*8Vq<bLW5T`<Slz;21!2$?Q8O81D1Cn3`XN910y+XT6vO5Y0?H0Pb_fS7NF
z%=-t2(_5l@8gdw94yU(8w*~STwa9V7Ve+m-c_Gv7a^OADZ517R>wVEZ3wfwIMi(sL
zgTP@#vFWx0Y+~v@5c7XRrd#&HhoXB9a+dP!gA1Ck2aZeB3y^8fCj3ZrFN%(B!q+(2
z#5<@{GvFeW=G%ZzC2FS~wQiqrqG2nDeD~%!@G<ZS@F~FOkI&Vn{YIE;JH)5eZip=h
zL*zSQnLrl6_r!Vv8312s`v&+H_)gV4V1xy|1(9zK9#hc=j5z%(m3_cyT=NH%?!UlK
zz&SM^h7AMIHvHjFAD}Og3G@T{0|V3!q$(N)k>6Bz20lWe`E~r|z!gAKpc!x_!0+=L
z04HE`5;z5X348^t0XXAU0_?C9pdHX2NCi9qZ^d!I2~_D6@Fj5Cg^_lKbp*Zz&H~>7
z-vd7a{{_widZ2H(?}CFjfe)bj5I73(&8R)Ve&AEYeFl6EY*Ej>Wi+lg8fv~SmkVSA
zLx3EY`uZ&+%QX}A*`j*CZKU1&Dw;DJsrcSrSD-2|6=BX#&c|Zl4uCU-*Y6Xs=Q9Ie
zXXG;h7yqvTtARDZ13+i*CvdIz;6=L=kN_kCNkB5-1{woRfGD5?dh1$%KSyW}qylL`
zB9H_m18vl}ca5;#tsyoA_{)sTfh&LpKtrGr&=`mS_zmcXz)^rdxY-H346Fqn1Req&
z1|CuGziUMH<SKtLU;#^jr2tp?_W{d*<-iKyet<uVS^(Sw@J)u10Ds-)0U830b#={q
z##N!`&|1BK-T)UGTtxI#Gu|_<DB{v339#QKLl<+AFa_Y1oG-R<Pv|jVIj{n_A1DF%
zwxI>^HOH~QIAA<b2uuJb0+Rr~t@k9b3E+)}cgSPtn2&&u1GEgSJz9M?0&fCb49^C*
zqU8#fOVz<ZXMk5K-fno;N&)7ge*Ef(E8%9ql|Znm#=md4i!#vc2>^e+!r!}G2{Z?q
z0Iwk3bHH|h-_Y|1SnGku0RCu;zZjbZGy%By3<FGnt0t}_4*`vk*9P!HfSYHJ0_%X)
zz#6{q$(JLS0DRT*AHaP8UIeu-ue`ZUM1{C8;zDRO&>W}>gaVl;WItd4z#o$o0uzAA
z0Jlu00@H!pfg)fQFdMi7m;=l;;AEa!bI@q+;^xM3wfmsas`JAT^8tQ;91FAno=3g-
z)&mzB{Ds_|02dSa0AIU&5O@eE24(`YT`KjEQLktla(Dvi&jEah<3k%CZ29p2B=8jQ
z0(|kuYy3eQR~vUD{hh!ZU^~+P6X1_qo(8yC!q;3E0NmO+jWlNf7xdg@83?q2kG8-M
zF6^~D0bw7&eGcwxL?B^(ReH!s>KqL*2;e$mIxqv63Csd!1H7kl6XFYiy9<W^F5l7t
zK8!>ItxYxTuyIS!VdQ(B_A31Yqizvby?ucUpd%0u9044?!+a3Ug`W4O@c<W73xI_H
z@7wbL$JSv!UY-Gt0(`7wI<EJ)z7v}wEfr;3iAX*m@=0(2z{fN`kMa5Ia)8fVT*dM6
zj*oMEeDjxgpicae-~&JjP#>rZ)B_Bl4iEs;0%`))fhvHeH?CN#o1rU0O}V_}Dr2$T
zNG%6KbSjktd8l95(c5gBtBxNSwTqaB4>$V(E-Sb$=no747`ORX^n4WIn&6XPiRVKC
zS9U|-BL`qvxa#i;_~jGm%g8Q}$G`k7ke2K7*{FXI5DvHiR=EHuuSa|T{-)XqJIwy{
zmw(w5%_@ujh0ZQyIt$4aF;~D`@%mM!tkJlPJqd8lTDhAj|GGK_a!kwXR^+HyhYQUd
z9e;1dHE0;H5lzJR&&FI(XEvJyf1lJnhje6rz2uczmT3+Qh64W8tt3I2y<Z)E#rH4r
zq+J7)B^($FnPCS`Rt~f77y5N<{dwy#19w&&E!3i8#vJ`-Z|HF&EL@LK(LWlky#0SP
z#v0x0<1#yR*Vm`##I3*Ez;(}e@GYrYh<W#gFXr#qYj!mBfiZ4Rg4<(XBSR1~Y4Q23
zJq`!7wPTVIp#>P~{7*)#5p1Z~b4IM*K;@n@;v(H}8iWM3Yo)bc{Lh2&ND$}txUnEi
zQk!Ye2?o_*u<Fq9?rPTf5x%@=k)ibSMugs9MV`l6ccjWdDB)I^*Mxb^uTJ$${r<y6
zFpqV|xMO{Hq#B5Mv2Sotiz7?wA|}Zl=Z?V@P?gZpEH!SXSyzp1Z3gP6)IO?yQX6B;
ziZOmx3B0le!)2$vSvUSCC3CH0mWYcmV%S2vf(C5-V$jK{5|e;+oGSR)h=^PY16J$f
zliTXQ*yP+kLyvJMxRFv@rPjbeuSjRrS5O*SpgO(43<->LCuy|;)xCLUh${}MBA2#D
zF|S(M$Bfexm(DYX>r<D0W5z`~-}sF9VOnuO+LkUTh6k;I)}5=;btGA(`hg;yf5$4h
zUAtz$1FJ^{=pJ`0`%`;MErdaW^LJWLw9eW2_S^zoiyZ^!54R$s_uu&FwiVmTV_K>s
z^zHmZ*YtfqADA?5&xmq^Y*oiF<MNz8C~Ecg+VT5FJ=I?diOik<(u!N0Gi67~>gk5w
z7=6p`b^dMZL`rt{uGpSIcA5k??tRYxae2dEIlOo8`H!XLJ;_?8+JNlzo$8o@8ady5
zjk#}6t=s2zSGD!saLl3T{4-axS028xdF03dct~=`#=DcTanRaCqa9Rx>Y!rIUnO<h
z^7GLT`X9a7u}jjrs561E>!W%Em|<odo=rC)oq--3JKC(Lj+<sL355HMF6L~fnh^kJ
z^VNy~l>S4thapi)fo@U_T_}+A*HN8z=H%>d{>^w4D8V<v3si3xhJ*7LU_-_=ZQr4K
ztz;N@+-*1o4ysk~P*%ddF0(;d2{)^ALCErT6~#)$suWP9^DkjP)X5!nK7RKy+f}^V
z6R&ksV`yiahc$}Ve$6sQ3lCS%2b$r(-H_4B)7K1DO{$mydQauaHA7tqn9>C*tqO9o
zn>j+4M)pmSdI7PVAZ%_kR-3AJR53fD39D2!Q_AY)OAzV&LD|6fPrG}ru76i~H)g3t
zRpH;)$TIin?yO4hZ-#M(+fy*SY+A;bP0KQ!i@JF#n=M$Prh+0b9C^qv-W`jp9==E1
z7-aU){Tn<?xvQCpk<S0&o&535nBH$MPq3RCQyD8$RaDK4XyE*H*{J_K6QBC^HFJEm
zh{IGbzfQzy+teNl1=+9C{sH=2>DA3v`Zp@Nx*3J*XhwAmuu1CF62vV~CDkz?{-vv!
z+GzUS)y-99fmt=oQxXVQ_bRi#su5|{_dZ|KY@~}DRlAlMUzPx=lhoo`W_`(7y;{p0
zfmknm8+w_m?sd$bXrvXGT)swUHz#3iV!FlYcB@Bt8w8uf0`;lhS>a}sVc@RvaDB5_
zT*RuZNOOcfRqc#4SLmJ8paw{1Q-t^N1}xoDmC@a-=RMZYtb#vb^OiO;ySOk^Jx!5i
zth%1m)tlSYTvcWod8HZVLgu*shI+4SW!}x0bGMsI)re@bt=~<fiFzm6jMo>cMXk*U
z)zl5S!d*n&<3`#1ZXXq7pQ)M~jYcZBX(H|_c#88g2>d;=8XQx>YsK4JW$wJSJ?8$1
zNawEud$xY*fr!_4hS($39(9A%xo&9NdCGG=n(UzW`s?wPyRUU4DvaL<@BQ7)%MCqM
zz0eaw+c$Mo(+o2aiS1e8H@Ve<3>4HiO}sB<n1NNL*Y&H^N4&S4ReC1!vy;q_kb0vp
znp$FPpP?!y(@b`vXJ(r9Ip<eqnz7LB%PcQOVwTwpD$Zu_$}HUWX@{FCu1o!FpNNT5
zM@OPiwv`M*kD2Vd21uAxXMp3@zKYU`b6HT&=V9piJBUyr10DYnYDk$Jr(Pe3DPWtr
z13mHX1YbT8YS<uDLN0CUzCq?}pHcZms9r!a-b(ka4tT4ocxkGq?jMX6_f=KhJJM{R
z0<$lymAW(Av2h0``>ri#vYl#`sq8j+cu09_>A-3^cAF!*qUrh^#}rkH!7bX?66)%q
z<$B4DuBD;;T0P7zu<ybj;fr=wLiN<NT(d#>Jt4-qMca9>pX3^D-!;O@`^^rEv$8C|
z7*<}Q@{*SKwpu;B+@QS3G9E)!qY);y*z8WIXL?Z2@<9=&-W%bc7Ar7mhO9hCU+KW%
zFutHkJ*q!C(7!W(*=A*hmiuC;8aT=sgc0f=qx?n_TTp#G3ggk&k+@?y!&?o@cZO+*
z+L&)PMn)JB@v2HTW?@+y`)VSEz)XL(!0E>Ferhz@8Cx=Dacd60@Scs^nXmQiZdIp8
zJGoaFQND~K)Wc((XnTZ&`AwJ#Bh4w8G^8ii?TNGH$_JAj<r{Nq$~Y&B5VdohU4L|j
z9a-NQOukUr^s9&Up17Dc9`kghS1&a2znh7w%>*=yZ}q9);+-=AOA$P^AT~lBz7@9y
z-<@0yoMe8X+bhV>CS2QBzCJ-E66DUV7T;#x;HxB`b5qsNx0!KrB@0p4O}3}yjLGGF
zf^>T)<N74&T`{=H_EF{(b2wG2r<6wpGD>uMAMdkM%u%dKvuWm*vYM!~)8N{^XGN%M
zrrV?W!kgtDCw}z|v;IGd(Wv%qvaWh_h8d`OQeoc!>#8QRa2ftdeKiBE@=P%<hCas?
zqD3WH=EJ4Gdw<szlKH$==FpC*ulmfysIK6=Vo1F=vn;D$daf+Jcfl+ySu3<yNT7$$
zRkx@k_nHyjX?K{b@t>@!;T$}(P50h0$2{kfyQ;c>K5{$g_0H$5)gBOGW+FO@4;2A;
zQnaUv-HTl2dYddXZ(>#x@5LqC9%jr6Q_7bWVm2&7{ko_+BzZPT@;#e`1=!s@c#+xN
zv6FlpeOW#YlQsJ<i1DEG!no##P8N0JvH%ZAT&np$mf(`J7?+$14=fyC$Rxbt5<#6`
zjN$KFc&cIxgFzOk_HuQFv!q<P1jAvH7t2#jCub$=#Y#4*YzgVbYV=OMTJbW>10Ks&
zlVxTH-!_M)1}`&<gJSrPGdRw8OP877>Z<Dsvxa(nt9hCC$O>~$1MjO_&9%q=4}Rsb
AA^-pY

delta 23020
zcmeHP33L=y)~+QT681onPDmi2AO^@Dl8^w+DlRM{zd~4q5CQ}eFa(rEOfV{fB1@r9
z5Cn7_1w=r=Mu$-lH*^pXcW?w$Fd!nRBclWJe_z$D*kD9w)bpSJ9P0E}-+lMpci&$1
z>UDL`+SH(OV}p6U^$+IvX;(JM(6k1cR(?ph5L_GKz2JJ_!QeXJ_#jQI3%(Ov6Fg10
z5tw!%VEVaM(wB}eDjbn8rZg|ui7zdfF#hJeA}znPbmEko3ba`0kxyB9j}T3ZLc%uS
z7;rssWAKR@n${G&7aR+I4jc_$2BzJe;6~taU@tfyT#H;<nlN!fK|y<XXTgg{j7u1i
zH!-iilVQ0J%1G!}f@^AJT6sZfeqK@Dh=Noon4k$x;ot^fwuCM<_}1fux1zeN>BHbl
zz!hK?=rH2h5~GU756>$q)kYKypEOF-wBk~na9kbc#K(g1DKCYS`ehJah7bzQADve;
z7KZ&AItAzsW|NQ3D@_<#ls76HVb-t&rDH+2g4q=v;Emw~aDDK)2&bizP)!yr3d|N-
z1U)$!Cy3=^N()Lo`N(jeHOq)A%tJ4*Uj4w#HXR%X?u1sRo91A4L__GCfmcAuVo%8{
zDk?0Us8t}`1mO}e{hXC-+>y`)BZGc7TVbZR^K6*1`(6Mu)t_);E)&7*o*`fs){Sqd
zJHw%i*e$mT&CC?HPtNa)85lt<`>qyFE^TELx3>0}YxTy~jG&~{aOhb6(Rszi1rx@U
z8VIx87%T+|Bgd7VMviRW;YE`spbJJH)vZH8y+hxHn#1GO4edNbb-kapD7cS4&8iIc
z>FYMc*YKM9v<;nWW}3}@vHLbGs?*1#H@7Mq^w0~fl!kH9?xb@k<3Db6hRh_Zu;DGC
z?ucHwp|WA7uIF!P7EwcwcZZ!jyxh@n&M-rH6E^gX^m_G~R$+oq|AuEzCsWfpS;YxC
zo^nW6P|{zq3KM;LrnM61xmG34Z&>k3KD~xjh;wgiCC-bjN}MmZ;*)*)O;%yD&+`oC
zNQPCMoMU_uY#mMRs9$2mr}#WWF_&|#gp?f5W=KPA$%{76x236&3T^2VNO`u@4J|&%
zmX<+sQk;b}#MTYM@b6Cv#ax-@(}S!^oUgLt(|!6qRw2$WSu1hA-m1iTixuD2=Q)k3
zk!7t(%+Zsrm2G``sa1*db}K%^r`NO!alXo0nc?%ig}K|)DxQ*~H?iU~efk|%A<p})
zl{n{Gl{nA0;<J3A+cC@9pgj$1ePX6@BFtKq)zOoLfz!=O$jtFnK<Yv%^i4?FFbuLv
zrex|N*2;E1eUw#+v)_u(_J#g{>70dl(~eKGR%ZM3+pJ2&ypHkJ9x=LgGC9)|gSpz?
zS_9wxA$76VWaa2PtjZi;=+&4g*V%R{*_oat2wlyJ8iymTRqZ>5cE|j=!j4{_ovAOh
z;yd^}uRw>^$jAwugK3<O*kJ2qS|&QVvV+e!6J@pP*wK@X>#W@rp1UA*WR9MfAPr+e
z&mc_O0d`$CKpJREbue{D*lt%rVy!(^NpfcBrwC;rggT-ojd1z6(JDqwCPTWywtt=y
zEDWn8A=49td7EV&&(6`?TZNrbpvD_kb^d;7S!-OS2n<s#5x^YOk^mlLhLZutrvaGC
zT3dieAkz-zvF$U#7?qk2V7e{<<GTVp0=SG90<D*={(YGR?IV5z8Fy2yAHag$fV-4^
z+~5!tzZ_=5T(PT;aktclNj#Y~&yz5j=|=!8;7I1r5{&_PkZDlN01h%8jAH-?nc?xm
zH-UMOsV@QOZxX-^ZUJ}%GW}1rwOA#%0=XVAOCm1BbbJqh+p;zfV0eM>LNN7<02XL5
zz@s`wx-xAEB3Ytk00+(E0LR37fDWDp7{3W%Mt<RE!Hj=S!dt-1c$<W`3%@M<Di{|m
z?QMYR-T{Kx{~ti0<9z@f91#8t%!AAVe9i!lKxVq5wzhhvzY_rMPKq6w1vtep`=0`q
zL)uw@2bl?IMK*+kz)a}Di3JLga448|wS?;k*Aor{(>`3-3&ux_!l{f28$lq)f@v5B
z#z$+))9=Kb6)nYoD=_nGqoe-JBLOEGq>Di&l{f;K@f}4^rh`sksygGuQQuwkJ;1EU
z)e`P0+#8IK)<?p9b+kVf*GWV_25^ugz@sHhro*vdFZg!R|7I@xy&U}qerQ^O6D!pC
zE(lc2<Vl#~-8@-KKl9opoGr0rHvGL3{!f_c?vr$6igShMiJr`<utfArIYO9lnHZ2M
zE|+k1O#4R=&&l_=*!>!&=@Vj4X8Wwx2RSuf0|gzg6$3KGbrQZ1^Rl*C?4K2To2@!W
zyjGhdQC80*QM?jpFCdn=Yy&g<m&7`dsd^bF@+%TgjzoCBg#Qb+^ZzXlko|8dz*uJr
zACMYch+7~d51NTBRfDJVn5s~muyoeyff)|tNm#dbe(o*f1tUsg8-bZ|j98HwA1C^z
zqW?9_e!Eoct7GQV+`uCm4O@yKnGu&WY!z3wEMwu5BsP#~l?*+jQYF4RW>mVwlbMbW
zYphl~(UU3WNH~C<;Siv(6Z*vPLd*=h!jAK}518S8l8(%2IY7eIG2@3AwVdwDhoNq*
zIqJ=0YjF#O?usQjnc)&J<0p!q%<wH>)_XFTi`zeg>3+57pA=pL)~v?IykYeAv{?BW
zYxO$jwQrJJ#P+YW#X)8tZ5RDZ5`G=bJa&OuhPQ;@5q=-cT=q-&V=zA2LD7H401h(k
zDh;_w9)*IHIVJ{VhQ9z)^`-DvU}pR^m=3;^@b_Ta{~+PB!a9s;A0%7@%qvzsFif;C
zQ*M$CprFG@Fdcb?qlBZuOb{dCSTGMV?Hda>5q%)j?<LTaF9Xxh<zjbvnJY*{D+!b7
zxV3N_(UU1A;Y5dNq7P&)C$dFPrk@-MpU-7f(2-9J0-1f?O&oNWc=G>1zex0p!K~2|
zFvrXzU><=?)nhm@pOwOE9J`Jh1<LCrVk4N2o}*Hj8ElboAnR70FI!sezYMXP@kLnB
z=w+Ok;#J`t!fy!g1mmOamhfA`ZwtQ*#z*@AC$`;(qTdf@lN^-rCt!}IW2W56k3+!(
zUxHcJQ(%0wZ*gLQz7zcq5<UaQ$2MlXfkX^@z|5#7nEKkHuP0$nW!i;HI5G&sn1)dj
z5hL6LOh=c3@zGj}z7?4EZ6ur|;S}LC(Pv0F3yhD}Ui3cUE@1lU#ybv6)I$_kNw}wk
zdrP>lg!@VO1_=+4@E|aU?{F|a+DM$3!Dul3kCkw-@OUsjT3M+Gw}5Fl70jBmV017;
zxI*IZlJMQavxV;!zE5}_m|Z#_Ouq}k_-GH}M8At2#<Ly-8Y~0jqdm&g&oJ%&jCkA#
zwDn+yH-a00Uy*eG8KeLIvji8mXBl3{iTtJ%D3G!9ro9I}TjqV*@s_$@6dy?fGMngQ
zFemh9q7P)oAAz2ZJ_obQj!XDU;S*rmodk1ioCfm-90X&wTn(^&8w+J>9OM?@L<y7W
zAQ{YzQiM~%ELawpj&i`X>mczRg?+-Ez{p$c2BzH=qVM5B&rxv|6g<dSh@JA5v7G%8
zqWggodm(0VhKYT?*atHGjD((-opE6Lp9*HRrh!?!GB7^c9Ufekm|!MD!Zf^F!eoYL
zfoXRynANyX;scrbd7=+wrn5v(roV*}Ug&A<l;}ZGT!?k+%-3;N?QiM>A)K|^QgQdN
zq$V@F6{3FxOuIiz{Dqk5R*D^&)mbgP2FzkVCGlVmXGg(=>m`EBgc~G$A*REPup@60
z`#|PE-U2;4@HH^=c>_%STOQ<3#XC^Y!Mk8){63g9-Y<Lr%!AA+SqY8?8yH4RXM&l|
zBjI4-8ln#st_7wme%r-#VO*gzVM8!8^nzKSMq&Wb-dRQe+WOynceTtXj=O+aqOJgs
zK;}-`AGEt#_A9nm?ehGpT~%6g_mwS}%K(n*n0-HtksN;%So1u92bm5=0PLEP0K0~}
zsyxVy|6O)hIsOB;RS~qq%z(S8JOcUm-d(jfW~$p(WyaLA1^&R@RcB-7Wy$DO@K3i_
z|H$1{v_I$L1-Dlb|A+0avi;e_7usHB{2#TuTGo_99|xJ!q9vGvFJODso*jSC?rPbu
z*j{y}=O44XivIu2+pp4s|COIkXzJ<`(x<z<dU1CZjp6LVvRN+fuG-D<f3V9s*0~WL
zkO6-oZh<mh++9T@RJW_j{-d53_KUl#&gK2$?kWbu#og75yQ`XZad$Ne&s$uCUff;1
zxV!pWc3b(_esOp8;_fPj0S@l6a>*6At4ck?yxLscU1e*WyX(rO{{P?I)hO(uvd4e2
zyXtROTd(-8j{Bp8r}6zzxLVP`=o;bv4k5p&FmX(&licTT7G%^g^kAiD8P{0J3CTch
zYgj^%x;D!Q(eF`Vml-j3cz>`F5?zjWcd?CC&aB6jjvv<+ybZdV{uN&1h#?N><c~me
z|A4y2X0LvaAEi20S1<RUNjFT>DSdxO-ADI7(as2;710t!<H464SX^#~eWv!jXf*6_
zm@<y3&~q4Ycj$=driqT<jyx;6TSdqH@#j>t?Qro8F2GEB8=~ri(ahSnyeQ+iPt3V5
z?|$Po7dmFcJ-}V6gf89^^9K=ykNtY-A$b5@EH3E!Q_(#nc45#RR@>;}dPH%4FGCdH
zZ^Db+RqtDF5*OjnEeCi!BRamjm!-m9f{S(#S&+YocfP*Y9^k=uIG8y)SnEIqj^{<k
z?+8*=9$ma<M`+qsiNdSb_7`w$!foO<7Bat#V-xa~5PbM!8?9Kar;BM2*<7zkR2<}6
zMfVzXEJagbI>2Lx=$b*kP3bSg1^1hI{7qb33b_KXdqH%~5$0Ef?1kN;YXSMF>Pr{y
zw~lX16o(MMnyd?c7dn>Wa^M5#c)TZe@sK}ME9k<#r|%PUe!FoMy9ftgX`zcYKrhjK
zB)SCXhO3iwF<PSd<_pb}fI`t75M45K6GX>1V`!HGOi`U*fr|wa^{GVh&2&q2d~t^6
z=|CswIK&P^$7oLHHflayxG(JS^%<IH0?$yuaa?p+kRK2oU$3Fa@$`^7NEh}Sya@7*
z8%E^-UjsZ&ijG6=8(V{|Xwh+~eXCMlg^Pbk)Hf34L-;$<eJi?72%i?+KSb9V;eV<b
zbn(4JotCJs2>&3ue~PXf!v7MT`=r_(;as(cE(Sv6lsO~jJs@8P@Zf7gtlX8rWLz&e
zsQ)dxs~{JsX0O4;y%2fuZ6d^K*8ukc%>WZRq||x>vw%y05V7k8dA2H{3x0>q!CG6Q
z`anJi-DTjqqPrII0)V5mzUcZww$wJdSPFBFNWP-P9Qy$eK*u9S%=uyt-&Eiz<ts_F
zyB?@eVLRZ0%XSXdOC_p5<muvqZzQoT1^|Vkix;>2vTCr(ql+6QDp|}2A!?}TQbaeH
zZlL4fPlXO2Z3xg^t*47@L~#yeLeByY1^A*0=RkYW4THQ2aw@npn9ZICv{3r%a1jO-
z5BKGye8}#8HD6F-MMePp{TGibL{|VgQuU<^L!z#fs8NVwba#N0p3$R$4_HJTSBtI?
z@;tSIF6L7W@OpsqYhwX66~{+k=ve3?fJ2bFe&V7S@(^{BF7hFAm|QRB;~~=p`@O&D
zZh}k~)a8nc63VLc8*srW4(P!WRSKD{P2CW2F%dG~cH)>BD!NIK_o(@F!5les3MA?l
z$aG8HDCpQ0lL5M=j#oKWZVHgA4!Wh_cbr9H&YJ?wsVf$9-W2$r4u{=%=-9H;fr%>R
zZz#p5P;vC#B2m049D&T^RxnFZ4ty@U>0p*(29T>}&;<+4%FGb+3dk%4b+?Q89gz8g
z7H#hUvlMp%&#OIjkq40j_#QEz2{~VMb3}JH!hF+-uIGx5lmAN9Y^O9QeuJmoFH!eE
zW^U9e=vWb6Q<$8(2gPm<<Xlxk7c}R*SR&?gA=8|?rJ|b$xgq4?;D^Nphh(_gMi);q
z8yqXd+=6_k=pGT>Lg?HtkRKJ@{m|vAus5X?jQ+DkDaZxDIN)*7Jt#Vk+$Ti0h&q)=
z7i@WsG)`uA)<Xa*N8OWRz63Je-VCe}-BQSYwVo~*H3e8JQOh9HEr-)Oal0Hc-BS0o
z*sXv(MCrTWf^UUQM{tvvKMHxW==`Gl6LeEV_bhbyXpaH8sxMv8`)$DU(6bdE2iPOj
zy&x{0fP5S>NA5P!t%Q6`t)Po9pejd@?<XQ7Z8gBk%>Z^lN4HM`tlS;I>*8V!<bmoW
zT`)&R?Ubmskm-V>beFhT2bnH7B01^V?CXJns`GBRp!sZIub6Lu+z2v<6W@SjDK-MU
z&u}=sBf3qH$Eo?Q3l5X_B+3t&ZWjaZi*B>%*jw(4l+QxWR|n~W1^g4RALcCJUjQ~S
zb$=HZTOiZzW5CCv`zz#Jm9hseXub+KBvCIwra7DN6VYuI9ovN07glZ?b!rA&M9};>
z;IKq(x1(0>F^)Ap;K3al_yqVA_zd8)2cI=Isr7FeUJu{@8K`zbjOROE1AtuM8lV@@
z8{j)@r+}{kK4QG7>g+YVp?o>;0LpSmCG0iQ^ph%YuMt=0duV<D{so*>b70t*Ki0Vc
z=no741_FbC!N84b8&WmkH_-fXP*>m(%HLF1U+y&`%lO@V3~&^>W55@{ao|f}Ilx)B
z6nGfO26BM*KnEZhNC9~JJ&J040UQTT0AB$ofv<sY0REc#JK&$d_rMtduhnJPee)XR
zoxn#>{vG%j;A>8A0(*eZ5O)|j0`P%;q<Z#kBQ7!@;s{_EkOvG0#-d(t8v{LjQSuqp
z>m9>){V=5Jfv78i8o*S9rvsdmoP&1&cLAIoN0E%r2z*PC&j4KPzY6eu&E>!ffJ=uS
z{)2F>_u|qb9cT+=0GU7n&;*DBngTwIT>d~k2WSs;06GHvEkXv631k8Bs`x#_+u>4(
zO@U^>B|r=i3p56r08s#c-ST(fV}QSuc@fwSJfgO}XEaRU%6>iYG_V2K2y6nlx}Og$
z04!i3@PN|aH@uhecT97Ddw|hEA;2G>B?GZQ6X2AlI=*kTis*xex)$gQaQ)B==%%K>
zZ(Q0r10pvp_)Zzu2U7rE#QAO-->6#!ECLn-4*^R6Zec9|_=e(mbr7W~yBXqBz*=A(
zz*`FMg$L05hk#Fj_GsF_fp-F2;Z^`#rgB-z^(WVx-2uL*y&B-{DI4JLyjp=T0h$9X
z0In3fqFuWI-3?9M@`2H!jNfkZcPIF%iq-<S42T2xQ=~1xa{zzD#P9T10;>T2%8Ebj
z;xaQ1;L<V@;5w4aDy|bh1i0*e5_})PHxeHQo&c5s%K^OASZ43(EkxjcKmmMpa}k`Z
zN8*X78J9g=<}3p)0~!F~zyK6-ATSu<4?`vZrNCrh3cyW~TY>4oZNTk71#ky&7cdi;
zZNSleYUzhYOV1*NA5uF$G+LL9g~;FDqyYTI&R<cl)c}_f{3#ok4qOY21^C9~qrjg4
zzHK@Kr~o#?GjFla0KBbk26!vwZ&3aPoCf&V#z)&XfVIGS;CcQOj=wtNFVTvT!7Mnt
z6SxcD?*RS+JPpGQz(#;?s?GtplXC)Tz5;@v=bp+CAPYX)0jFWJ2D}^KYsK8fh=MK}
zhy$7e&45ch>eN1?)fFL-xpL()q8yk3+zwO#T=a4Op%UQ6!iRuDxjF$|fi^%Q@QSX6
z?>BBJJ41VbcRJpx1_6Tsu88{sy@9Sk8n7R5^)B;)bOiJTKncKw)*OIOp1ig59`4$@
z%*V{HfRFj>Up`+l;R1k*Ki8-O3dx5_J}B~ua4^8fH9nW|IjkAL<r-IbNdO=9QqGgN
zqLKNtzZJj|AR1@@L;yNaU&rmlgisf#4b%jJfq$bQBLKHne4MxqH7x|s1Lgy{YTJIJ
zVLP|H!=cIt-0=B)egiwnL%H<X12{nsSA~vPhtD8fAaFt8s2cNufGfK^<m2YY75<fg
zW1>4$=H}iK)?9s8pi&`#7YG7ak)nXoo?p7a%F~w{c9}gED7*OyXa8h8RcBnCPAbo(
zFqghu>N=`sT(aUfEE?CRKcBLy=5C2xQ#breHRYgp)3BCI7mX~r_~c^qDYPD6D&x~d
zpdDKjD6^aP{D>_`NP|GVTOgO+4h<~eh3wsQf%b!u|IcQ0egPQfmW&tRa}mQX+>Bjj
zOH`NMwRd@Q2=@wcG*|Nv8Z-T|hm7zjJyNB7Z?yIg_}-Xkc<bS!_1+6d=WN?;b~E%F
zlM_?gCZ=fbsx$vGqK%`viaKMY8mD#D6RaDm?2M5X6Aq`LNLRm}ufu{ZAEqN+T4G8f
zE=rBnP8jH|m44QU*4wHWQWw?stdZ9CTBNFtRIT6Ik^Q#5EX~O+1-bFgR0lD2P8{#u
z@$|>@5tEvjoQQ>Fp;`(LF_U1>00zG6>kge#XZH#iWG1F1V#Qgg_AraRYHhL^tshdI
zE6j#!d7>GtON>J?AwiCZ?z;!}`z|N<QOP0$BS{ti8(GA|pf0lL*&@?>-=xo{!vGm1
zrsLYKR=_~NQ=RxJ?NdW<F}*>VSmmn&A!c}RT4E*^w`%JkGdwOWF++3z(n(dGncODy
z5SuC$C2xzX-TftInjU-qW9BG5PnFd)qt#`)*+|b<opn@YoEij*asPX&Ea<vRPtU)5
zPmrFHn93f~mZ`Zg$Z-FxYDaMEJ8R)zh<-G1|IX^Er2Osg++B<djhpmD4fS{3jEZsp
z04rg1OHb|`&wD>wv{7LO3eiKgHc-g+YQ;n|JR4&Jjfjq7FQzTXpSo@7a<r2N8up(1
zZ&t^0^73}1_6|i=Qxe;<1yj`;dh@AQ>20N2zrc)a0$=VIRO27qRqwXDd#d{S1~}r#
zs!&NLQar47g_#j?>1b5<zpy%ouP$t~_J(QFffxd@YDN%j6I87lpblz1Q(mox&PT^2
zyPcuFH_ht|=^0O33?19hYbLwt?P5jir7CHG8KGVeGMmJ>f0y<76SM2R`)=?}$TJ;d
z0(pvYK>6Ii%<7%=;v><oZx4rUT4E|&th?&#L5cdQu^!aEL@h%o*8NAVJp)$lzUrH0
zpBd;Jdj?e%_;Zih<eUO8Qq6+lXPru^X?h#C)!aYZ8g^BFtxINI|Dl~@3QL!y#=|nk
z{r9bdXLmkzd3v1(VSo|9BH2Eo^(0l+9Cq&CXK5p!f9d$VlKrqth22k@wxOEa2L(u1
zJ~VWU`{!Lf4i$B8wax!5Qe|>z2DEHdCpfbsR=qgLY=Fj3)$-K78fJGicC!#OM=c*{
z2Dx#`nv)70gNs%O^6R8llbp5==2&ptMd|z0ih*XdG*Xn|&Ow?uJzb^qP*<X+g`(i@
zzlR-LUGUh$Z!LSqDR_EfmOUjSJh+r;E7XoqvzH#&<h^8;B3I1$=veoUzs7#|OnS#t
zU1mCN(r^`YM;Ybi%J%>Yzgg+EKyRv;T4rm{2aS0}4x4O71!u9DyQsoi7#c&=3=59k
z|0VnAh5buL?Y?0L9HUox$(Y?l4XbDNR84A|qYWnzrIyz=k4hjyJ+92g{^#qMSvsz|
zbgEj_Gt(t{q!dFPs8^LfLJh5N9<NHDA7=K|?a3E`e#+GBMvvCFs>(1FaGyFj#EkZ5
zG%!a7OKbR_i84bB-YB*<GRv#{z-ePOF~%J2m^D)0#h5d2TUZl|%Ut+mGa@*H6>sf_
z0)H?zO~qXX$7dUxLHM6DzZqxt@L-@{!_isoFi+;qCdrTCy9}xQNS)wuIwrz@?{srE
zT^?^^?vlHJnwMlYQeP#Q>G~wKItgXym<ahNca3r9-ZjKP4MyrfPvYdK>4VkjA5G8m
zRjBfk(S|>J!>T5!KRLzxTeP07&RmVb(_MM5u`eJ?=ODDzUvLe+33r+<5*IoxaIkv)
z8+)3~4c(qX;p%vAvz^Qq)u|6^?o0*Up3!M~g@1b=GcH8>SLdB6LNyp*Hi0H(05Y@f
zBGj+}W|64W;Q??X(Iu*FE@q1pQcH5pMx2wIbInxfjyX}Bsp`6cW^a9;A97<|rfpA{
zU{s~4PJ_|Nj_gp1nWY7WIsQsU+xc)9o<Hr?`-OIa>CAButrBmv3rTZzt0S+!5d+b7
zn;4vuj#*{P(W-nXN{uVX@?mC_+Bnp_9Yz6Hm&l+j_a3jxhnYcvxg!Z{k%#-aYMOU`
zE!6TnGlS71+;XL<j>B!MK-E@9WtYC8s+VuqQw-)iA;h7~T$4D>qOKTmz8j}zRgK9a
zIoE4+UI8YTQ(4xtya2spU&?t8bS|#;Rnj(L+sk#=wuwMgRc|<&0EcYvJtN(o2`uA}
zeW;!rbzWgPtWZy{${uaT%Edd<OhM&-?Bz6dYP6dyIS6xppXyxbW>oDE$U3K~Q`F64
z%vh?}64Goxj<l-c$$}w5O&;qGqG+{ltTQCoRO;+l3?-+)RpU~X7un+r<0M?|C^F;R
z9DW=HRXMo1J5w@ReNk-Ji$k+&*kzA%-MLi@x0fn`_fd{d>Ay5pYrI{nfN|uMAX;r5
zZ@WUZ1BZEF*HjhAZWY8}Wsv5K4{=g8Ozh?x1+87tk4o%{rcAK&CRKG_8v2KmMm;zI
z6X;s?(apH3r<bBh^;qS*6%j=fQEg`h7=(pLdp}l|Ifi43dFjWk)zo9${}(+rX4+k$
z*%`~+duxWXCWr_|S=-8ORy{e{yv`|GG#WR03d)11<x??_M^14YG+eEn;`UE;EVrMz
zh>dJ`xaZ|}U%AcppOS_3x}<cLkr66&s(D@Y*7h<`tYfP6XIt2-%y9pasb+s^gH3_e
z=fXN#T|OO+Qm9tUfIOJ8y~H@@PIkX~vdqkOjM7x2a@1>*>Qj!XuveWxI_J?RTJ0{!
z71nX(#z;u~-!;R{E6wr1ho>JsG_p%^j*9mz7Oh%Rr*3!so$KhlRQ@L_cyG5yfwVw)
zFrWQ2`{5$eZ{2A=ihmka%VwI9s@F^`L-PHQ&v<aJ!VNDCH@rP_etvAUMc+9q`k)NC
zP)9<NG2+jfYu>=z=gvcm*~5>y<EH84&Y>8q!serr392=zgE~mEXI|B0RP`5Np}Jr`
z8egm(Gr1Xr+wpDyGdD}KpC+Q69AycMJJABO(Jww*xNdBp=PX3MD$+8?IIG28?#j|x
zXDVYM=AEqkHZR0baMqOmruTCWp0gPBPj~=2cg5z%%5j!nbJ!y6KHxa8NM$cFJE`(T
oW_f7+#g1~1KXkGAjqdj@F?YqP&?RPF)&DPM9siNd<|7CH7X}os(f|Me

diff --git a/package.json b/package.json
index 219b2e0..dd31430 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
     "@solid-primitives/map": "^0.4.13",
     "@solid-primitives/page-visibility": "^2.0.17",
     "@solid-primitives/resize-observer": "^2.0.26",
+    "@solid-primitives/rootless": "^1.4.5",
     "@solidjs/router": "^0.15.1",
     "@suid/icons-material": "^0.8.1",
     "@suid/material": "^0.18.0",
diff --git a/src/platform/MediaQuickview.css b/src/platform/MediaQuickview.css
new file mode 100644
index 0000000..b1ceb07
--- /dev/null
+++ b/src/platform/MediaQuickview.css
@@ -0,0 +1,69 @@
+.MediaQuickview__root {
+  display: contents;
+}
+
+.MediaQuickview {
+  border: none;
+  position: fixed;
+  width: 100vw;
+  width: 100dvw;
+  height: 100vh;
+  height: 100dvh;
+  max-width: 100vw;
+  max-height: 100vh;
+  contain: content;
+  padding: 0;
+
+  &::backdrop {
+    background: none;
+  }
+
+  >.Scaffold>.topbar {
+    position: fixed;
+    left: 0;
+    right: 0;
+
+    >* {
+      background-color: var(--tutu-color-surface);
+      color: var(--tutu-color-on-surface);
+    }
+  }
+
+  >.Scaffold>.pages {
+    display: grid;
+    grid-auto-flow: column;
+    grid-auto-columns: 100%;
+    height: 100%;
+    width: 100%;
+    overflow: auto hidden;
+    scroll-snap-type: x mandatory;
+    scroll-snap-align: center;
+    scroll-snap-stop: always;
+
+
+    >.page {
+      width: 100%;
+      height: 100%;
+      max-width: 100vw;
+      max-height: 100vh;
+      contain: layout style size paint;
+
+      >* {
+        display: block;
+        width: 100%;
+        height: 100%;
+
+        object-fit: contain;
+        object-position: center;
+      }
+    }
+  }
+
+  &.lightout {
+    >.Scaffold {
+      >.topbar {
+        visibility: hidden;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/platform/MediaQuickview.tsx b/src/platform/MediaQuickview.tsx
new file mode 100644
index 0000000..2a9d7e1
--- /dev/null
+++ b/src/platform/MediaQuickview.tsx
@@ -0,0 +1,179 @@
+import { createCallback, createSubRoot } from "@solid-primitives/rootless";
+import {
+  createRenderEffect,
+  createSignal,
+  Index,
+  Match,
+  onCleanup,
+  onMount,
+  Switch,
+  type Component,
+} from "solid-js";
+import { render } from "solid-js/web";
+import Scaffold from "~material/Scaffold";
+import { isPointNotInRect } from "./dom";
+import "./MediaQuickview.css";
+import AppTopBar from "~material/AppTopBar";
+import { IconButton } from "@suid/material";
+import { Close } from "@suid/icons-material";
+
+function renderIsolateMediaQuickview(
+  each: QuickviewMedia[],
+  index: number,
+  transitionFrom?: Element,
+) {
+  createSubRoot((disposeAll) => {
+    let container: HTMLDivElement;
+
+    createRenderEffect(() => {
+      container = document.createElement("div");
+      container.setAttribute("role", "presentation");
+      container.classList.add("MediaQuickview__root");
+      document.querySelector("body")!.appendChild(container);
+
+      onCleanup(() => container.remove());
+
+      const dispose = render(() => {
+        return (
+          <MediaQuickview
+            each={each}
+            defaultIndex={index}
+            transitonFrom={transitionFrom}
+            onClose={disposeAll}
+          />
+        );
+      }, container);
+
+      onCleanup(dispose);
+    });
+  });
+}
+
+export function createMediaQuickview() {
+  return createCallback(renderIsolateMediaQuickview);
+}
+
+function ImagePage(props: { src: string; alt?: string }) {
+  const [scale, setScale] = createSignal(1);
+  const [offsetX, setOffsetX] = createSignal(0);
+  const [offsetY, setOffsetY] = createSignal(0);
+
+  const onWheel = (event: WheelEvent & { currentTarget: HTMLElement }) => {
+    // This is a de-facto standard for scaling:
+    // Browsers will simulate ctrl + wheel for two point scaling gesture.
+    if (event.ctrlKey) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      const offset = event.deltaY;
+
+      setScale((x) => x + offset / event.currentTarget.clientHeight);
+    }
+  };
+
+  return (
+    <img
+      src={props.src}
+      alt={props.alt}
+      onWheel={onWheel}
+      style={{
+        transform: `scale(${scale()}) translateX(${offsetX()}) translateY(${offsetY()})`,
+      }}
+      onLoad={({ currentTarget }) => {
+        const { top, left, width, height } =
+          currentTarget.getBoundingClientRect();
+      }}
+    ></img>
+  );
+}
+
+export type QuickviewMedia = {
+  cat: "image" | "video" | "gifv" | "audio" | "unknown";
+  src: string;
+  alt?: string;
+};
+
+export type MediaQuickviewProps = {
+  each: QuickviewMedia[];
+  defaultIndex: number;
+  transitonFrom?: Element;
+
+  onClose?(): void;
+};
+
+const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
+  let root: HTMLDialogElement;
+  const [lightOut, setLightOut] = createSignal(false);
+
+  function onDialogClick(
+    event: MouseEvent & { currentTarget: HTMLDialogElement },
+  ) {
+    event.stopPropagation();
+
+    if (
+      isPointNotInRect(
+        event.currentTarget.getBoundingClientRect(),
+        event.clientX,
+        event.clientY,
+      )
+    ) {
+      event.currentTarget.close();
+    } else {
+      setLightOut((x) => !x);
+    }
+  }
+
+  return (
+    <dialog
+      ref={(e) => {
+        root = e;
+        onMount(() => {
+          e.showModal();
+        });
+      }}
+      class="MediaQuickview"
+      classList={{ lightout: lightOut() }}
+      onClose={props.onClose}
+      onCancel={props.onClose}
+      onClick={onDialogClick}
+    >
+      <Scaffold
+        topbar={
+          <AppTopBar>
+            <IconButton color="inherit" onClick={(e) => root.close()}>
+              <Close />
+            </IconButton>
+          </AppTopBar>
+        }
+      >
+        <div
+          ref={(e) => {
+            onMount(() => {
+              e.children.item(props.defaultIndex)!.scrollIntoView({
+                behavior: "instant",
+                inline: "center",
+              });
+            });
+          }}
+          class="pages"
+        >
+          <Index each={props.each}>
+            {(item, index) => {
+              return (
+                <section class="page">
+                  <Switch>
+                    <Match when={item().cat === "image"}>
+                      <ImagePage src={item().src} alt={item().alt} />
+                    </Match>
+                  </Switch>
+                </section>
+              );
+            }}
+          </Index>
+        </div>
+      </Scaffold>
+    </dialog>
+  );
+};
+
+export default MediaQuickview;
diff --git a/src/timelines/toots/MediaAttachmentGrid.tsx b/src/timelines/toots/MediaAttachmentGrid.tsx
index c9d72fc..6291a86 100644
--- a/src/timelines/toots/MediaAttachmentGrid.tsx
+++ b/src/timelines/toots/MediaAttachmentGrid.tsx
@@ -5,13 +5,9 @@ import {
   Match,
   Switch,
   createMemo,
-  createRenderEffect,
   createSignal,
-  onCleanup,
   untrack,
 } from "solid-js";
-import MediaViewer from "../MediaViewer";
-import { render } from "solid-js/web";
 import {
   createElementSize,
   useWindowSize,
@@ -24,6 +20,7 @@ import cardStyle from "~material/cards.module.css";
 import { Preview } from "@suid/icons-material";
 import { IconButton } from "@suid/material";
 import Masonry from "~platform/Masonry";
+import { createMediaQuickview } from "~platform/MediaQuickview";
 
 type ElementSize = { width: number; height: number };
 
@@ -58,39 +55,23 @@ const MediaAttachmentGrid: Component<{
   sensitive?: boolean;
 }> = (props) => {
   const [rootRef, setRootRef] = createSignal<HTMLElement>();
-  const [viewerIndex, setViewerIndex] = createSignal<number>();
-  const viewerOpened = () => typeof viewerIndex() !== "undefined";
   const settings = useStore($settings);
   const windowSize = useWindowSize();
   const [reveal, setReveal] = createSignal([] as number[]);
 
-  createRenderEffect(() => {
-    const vidx = viewerIndex();
-    if (typeof vidx === "undefined") return;
-    const container = document.createElement("div");
-    container.setAttribute("role", "presentation");
-    document.body.appendChild(container);
-    const dispose = render(() => {
-      onCleanup(() => {
-        document.body.removeChild(container);
-      });
-
-      return (
-        <MediaViewer
-          show={viewerOpened()}
-          index={viewerIndex() || 0}
-          onIndexUpdated={setViewerIndex}
-          media={props.attachments}
-          onClose={() => setViewerIndex()}
-        />
-      );
-    }, container);
-
-    onCleanup(dispose);
-  });
+  const openMediaQuickview = createMediaQuickview();
 
   const openViewerFor = (index: number) => {
-    setViewerIndex(index);
+    openMediaQuickview(
+      props.attachments.map((item) => {
+        return {
+          cat: item.type,
+          src: item.url as string,
+          alt: item.description || undefined,
+        };
+      }),
+      index,
+    );
   };
 
   const columnCount = () => {

From bffa896184d8e1cf8c62366fe2383e64132d1b8c Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 20:45:29 +0800
Subject: [PATCH 2/7] ci: enforce type checking

---
 .forgejo/workflows/depoly.yml | 3 +++
 package.json                  | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/.forgejo/workflows/depoly.yml b/.forgejo/workflows/depoly.yml
index edba093..0ad6d10 100644
--- a/.forgejo/workflows/depoly.yml
+++ b/.forgejo/workflows/depoly.yml
@@ -30,6 +30,9 @@ jobs:
       - name: Install Dependencies
         run: bun install
 
+      - name: Validate types
+        run: bun typecheck
+
       - name: Build Dist (Staging)
         run: VITE_CODE_VERSION=$GITHUB_SHA bun dist -m staging
         if: env.GITHUB_REF_NAME == 'master'
diff --git a/package.json b/package.json
index 219b2e0..4e04928 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,8 @@
     "dev": "vite --host 0.0.0.0",
     "preview": "vite preview",
     "dist": "vite build",
-    "count-source-lines": "exec scripts/src-lc.sh"
+    "count-source-lines": "exec scripts/src-lc.sh",
+    "typecheck": "tsc --noEmit --skipLibCheck"
   },
   "keywords": [],
   "author": "Rubicon",

From 0fca81dc93d1bcfb1c02fb00ee21c03ba9e1247d Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 22:57:41 +0800
Subject: [PATCH 3/7] polyfills: add Promise.withResolver

---
 src/platform/polyfills.ts     | 21 +++++++++++++++++++++
 types/lib.esnext.promise.d.ts | 26 ++++++++++++++++++++++++++
 2 files changed, 47 insertions(+)
 create mode 100644 types/lib.esnext.promise.d.ts

diff --git a/src/platform/polyfills.ts b/src/platform/polyfills.ts
index 032207a..1af3341 100644
--- a/src/platform/polyfills.ts
+++ b/src/platform/polyfills.ts
@@ -15,3 +15,24 @@ if (typeof window.crypto.randomUUID === "undefined") {
       ) as `${string}-${string}-${string}-${string}-${string}`;
     };
 }
+
+
+if (typeof Promise.withResolvers === "undefined") {
+  // Chrome/Edge 119, Firefox 121, Safari/iOS 17.4
+
+  // Promise.withResolvers is generic and works with subclasses - the typescript built-in decl
+  // could not handle the subclassing case.
+  Promise.withResolvers = function <T>(this: AnyPromiseConstructor<T>) {
+    let resolve!: PromiseWithResolvers<T>["resolve"], reject!: PromiseWithResolvers<T>["reject"];
+    // These variables are expected to be set after `new this()`
+
+    const promise = new this((resolve0, reject0) => {
+      resolve = resolve0;
+      reject = reject0;
+    })
+
+    return {
+      promise, resolve, reject
+    }
+  }
+}
diff --git a/types/lib.esnext.promise.d.ts b/types/lib.esnext.promise.d.ts
new file mode 100644
index 0000000..5df9b56
--- /dev/null
+++ b/types/lib.esnext.promise.d.ts
@@ -0,0 +1,26 @@
+interface AnyPromiseWithResolvers<T, Instance> {
+  promise: Instance;
+  resolve: (value: T | PromiseLike<T>) => void;
+  reject: (reason?: any) => void;
+}
+
+type AnyPromiseConstructor<T> = new (
+  executor: (
+    resolve: PromiseWithResolvers<T>["resolve"],
+    reject: PromiseWithResolvers<T>["reject"],
+  ) => void,
+) => Promise<T>;
+
+interface PromiseConstructor {
+  /**
+   * Creates a new Promise and returns it in an object, along with its resolve and reject functions.
+   * @returns An object with the properties `promise`, `resolve`, and `reject`.
+   *
+   * ```ts
+   * const { promise, resolve, reject } = Promise.withResolvers<T>();
+   * ```
+   */
+  withResolvers<T, K extends AnyPromiseConstructor<T>>(
+    this: K,
+  ): AnyPromiseWithResolvers<T, InstanceType<K>>;
+}

From f0456337d5b76d2a7deca73ccf547dbb2c9435f8 Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 23:19:04 +0800
Subject: [PATCH 4/7] ci: add checkpr

---
 .forgejo/workflows/checkpr.yml | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 .forgejo/workflows/checkpr.yml

diff --git a/.forgejo/workflows/checkpr.yml b/.forgejo/workflows/checkpr.yml
new file mode 100644
index 0000000..bb88906
--- /dev/null
+++ b/.forgejo/workflows/checkpr.yml
@@ -0,0 +1,34 @@
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened]
+
+jobs:
+  depoly:
+    runs-on: fedora-41
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Bun
+        uses: oven-sh/setup-bun@v2
+        with:
+          bun-version-file: 'package.json'
+
+      - name: Cache Dependencies
+        id: dependencies-cache
+        uses: actions/cache@v4
+        with:
+          path: ~/.bun/install/cache
+          key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
+          restore-keys: |
+            ${{ runner.os }}-bun-
+
+      - name: Install Dependencies
+        run: bun install
+
+      - name: Validate types
+        run: bun typecheck
+
+      - name: Build Dist
+        run: VITE_CODE_VERSION=$GITHUB_SHA bun dist

From f20b5da8e4c7c340c156e5fd788fcda6b789dd6c Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 23:42:16 +0800
Subject: [PATCH 5/7] serviceworker: fix type errors

---
 src/serviceworker/main.ts       | 31 ++++++++++-------
 src/serviceworker/tsconfig.json |  6 ++--
 src/serviceworker/workerrpc.ts  | 61 +++++++++++++++++++--------------
 tsconfig.json                   | 19 +++-------
 tsconfig.super.json             | 17 +++++++++
 5 files changed, 80 insertions(+), 54 deletions(-)
 create mode 100644 tsconfig.super.json

diff --git a/src/serviceworker/main.ts b/src/serviceworker/main.ts
index 71a4f37..99305a9 100644
--- a/src/serviceworker/main.ts
+++ b/src/serviceworker/main.ts
@@ -4,23 +4,27 @@ import { dispatchCall, isJSONRPCCall, type Call } from "./workerrpc";
 
 function isServiceWorker(
   self: WorkerGlobalScope,
+  // @ts-ignore: workaround for workbox logger.d.ts decl
 ): self is ServiceWorkerGlobalScope {
-  return !!(self as unknown as ServiceWorkerGlobalScope).registration;
+  return (
+    (self as unknown as Record<string, unknown>)["serviceWorker"] instanceof
+    ServiceWorker
+  );
 }
 
-if (isServiceWorker(self)) {
-  cleanupOutdatedCaches();
-  precacheAndRoute(self.__WB_MANIFEST, {
-    cleanURLs: false,
-  });
-
-  // auto update
-  self.skipWaiting();
-  clientsClaim();
-} else {
+if (!isServiceWorker(self)) {
   throw new TypeError("This entry point must be run in a service worker");
 }
 
+cleanupOutdatedCaches();
+precacheAndRoute(self.__WB_MANIFEST, {
+  cleanURLs: false,
+});
+
+// auto update
+self.skipWaiting();
+clientsClaim();
+
 export const Service = {
   ping() {},
 };
@@ -29,6 +33,9 @@ self.addEventListener("message", (event: MessageEvent<unknown>) => {
   const payload = event.data;
   if (typeof payload !== "object") return;
   if (isJSONRPCCall(payload as Record<string, unknown>)) {
-    dispatchCall(Service, event as MessageEvent<Call<unknown>>);
+    dispatchCall(
+      Service,
+      event as MessageEvent<Call<unknown>> & { source: MessageEventSource },
+    );
   }
 });
diff --git a/src/serviceworker/tsconfig.json b/src/serviceworker/tsconfig.json
index bc88f57..090edc2 100644
--- a/src/serviceworker/tsconfig.json
+++ b/src/serviceworker/tsconfig.json
@@ -1,5 +1,7 @@
 {
   "compilerOptions": {
-    "lib": ["ESNext", "WebWorker"],
+    "lib": ["WebWorker", "ESNext"],
   },
-}
\ No newline at end of file
+  "extends": ["../../tsconfig.super.json"],
+  "include": ["./**/*.ts"],
+}
diff --git a/src/serviceworker/workerrpc.ts b/src/serviceworker/workerrpc.ts
index 84bb39f..7680716 100644
--- a/src/serviceworker/workerrpc.ts
+++ b/src/serviceworker/workerrpc.ts
@@ -46,7 +46,7 @@ export type Result<T, E> = JSONRPC & { id: string | number } & (
 export function isJSONRPCResult(
   object: Record<string, unknown>,
 ): object is Result<unknown, unknown> {
-  return object["jsonrpc"] === "2.0" && object["id"] && !object["method"];
+  return !!(object["jsonrpc"] === "2.0" && object["id"] && !object["method"]);
 }
 
 export function isJSONRPCCall(
@@ -114,6 +114,9 @@ export class ResultDispatcher {
     {
       const callback = this.map.get(id);
       if (!callback) return;
+      // Now the callback is not undefined
+
+      // Fast path
       if (typeof callback !== "boolean") {
         callback(message);
         this.map.delete(id);
@@ -121,28 +124,30 @@ export class ResultDispatcher {
       }
     }
 
-    return new Promise<void>((resolve) => {
-      let retried = 0;
+    const { promise, resolve } = Promise.withResolvers<undefined>();
 
-      const checkAndDispatch = () => {
-        const callback = this.map.get(id);
-        if (typeof callback !== "boolean") {
-          callback(message);
-          this.map.delete(id);
-          resolve();
-          return;
-        }
-        setTimeout(checkAndDispatch, 0);
-        if (++retried > 3) {
-          console.warn(
-            `retried ${retried} time(s) but the callback is still disappeared, id is "${id}"`,
-          );
-        }
-      };
+    let retried = 0;
 
-      // start the loop
-      checkAndDispatch();
-    });
+    const checkAndDispatch = () => {
+      const callback = this.map.get(id);
+      if (typeof callback !== "boolean") {
+        callback!(message); // the nullability is already checked before
+        this.map.delete(id);
+        resolve(undefined);
+        return;
+      }
+      setTimeout(checkAndDispatch, 0);
+      if (++retried > 3) {
+        console.warn(
+          `retried ${retried} time(s) but the callback is still disappeared, id is "${id}"`,
+        );
+      }
+    };
+
+    // start the loop
+    checkAndDispatch();
+
+    return promise;
   }
 
   createTypedCall<
@@ -159,14 +164,16 @@ export class ResultDispatcher {
   }
 }
 
-type AnyService = Record<string, ((...args: unknown[]) => unknown) | undefined>
+type AnyService = Record<string, (...args: unknown[]) => unknown>;
 
-export async function dispatchCall<
-  S extends AnyService,
->(service: S, event: MessageEvent<Call<unknown>>) {
+export async function dispatchCall<S extends Partial<AnyService>>(
+  service: S,
+  event: MessageEvent<Call<unknown>> & { source: MessageEventSource },
+) {
   try {
     const fn = service[event.data.method];
     if (!fn) {
+      console.warn("requested unknown method", event.data.method, event.data);
       if (event.data.id)
         return event.source.postMessage({
           jsonrpc: "2.0",
@@ -176,10 +183,12 @@ export async function dispatchCall<
             message: "Method not found",
           },
         } as Result<void, void>);
+
+      return;
     }
 
     try {
-      const result = await fn(...event.data.params as unknown[]);
+      const result = await fn(...(event.data.params as unknown[]));
 
       if (!event.data.id) return;
 
diff --git a/tsconfig.json b/tsconfig.json
index b92e528..feaab58 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,20 +1,11 @@
 {
   "compilerOptions": {
-    "strict": true,
-    "target": "ESNext",
-    "module": "esnext",
-    "moduleResolution": "node",
-    "allowSyntheticDefaultImports": true,
-    "esModuleInterop": true,
     "jsx": "preserve",
     "jsxImportSource": "solid-js",
     "types": ["vite/client", "vite-plugin-pwa/solid"],
-    "noEmit": true,
-    "isolatedModules": true,
-    "resolveJsonModule": true,
-    "paths": {
-      "~platform/*": ["./src/platform/*"],
-      "~material/*": ["./src/material/*"]
-    }
-  }
+    "lib": ["ESNext", "DOM", "DOM.Iterable"]
+  },
+  "include": ["./src/**/*.ts", "./src/**/*.tsx", "./*.ts", "./types/**.ts"],
+  "exclude": ["./src/serviceworker/**"],
+  "extends": ["./tsconfig.super.json"]
 }
diff --git a/tsconfig.super.json b/tsconfig.super.json
new file mode 100644
index 0000000..9cf8e60
--- /dev/null
+++ b/tsconfig.super.json
@@ -0,0 +1,17 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "target": "ESNext",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "noEmit": true,
+    "isolatedModules": true,
+    "resolveJsonModule": true,
+    "paths": {
+      "~platform/*": ["./src/platform/*"],
+      "~material/*": ["./src/material/*"]
+    }
+  }
+}

From 4d4e787b84f1026417567ea29441572819cadad1 Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 23:45:59 +0800
Subject: [PATCH 6/7] ci/checkpr: correct the job name

---
 .forgejo/workflows/checkpr.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.forgejo/workflows/checkpr.yml b/.forgejo/workflows/checkpr.yml
index bb88906..2f6dc9f 100644
--- a/.forgejo/workflows/checkpr.yml
+++ b/.forgejo/workflows/checkpr.yml
@@ -4,7 +4,7 @@ on:
     types: [opened, synchronize, reopened]
 
 jobs:
-  depoly:
+  checkpr:
     runs-on: fedora-41
     steps:
       - name: Checkout

From a15e2d62fe7dd5e5e7be78648a767f4e7c84a71f Mon Sep 17 00:00:00 2001
From: thislight <l1589002388@gmail.com>
Date: Wed, 4 Dec 2024 20:02:29 +0800
Subject: [PATCH 7/7] first prototype of MediaQuickview

---
 bun.lockb                                   | Bin 301673 -> 301705 bytes
 package.json                                |   1 +
 src/platform/MediaQuickview.css             |  69 ++++++++
 src/platform/MediaQuickview.tsx             | 179 ++++++++++++++++++++
 src/timelines/toots/MediaAttachmentGrid.tsx |  43 ++---
 5 files changed, 261 insertions(+), 31 deletions(-)
 create mode 100644 src/platform/MediaQuickview.css
 create mode 100644 src/platform/MediaQuickview.tsx

diff --git a/bun.lockb b/bun.lockb
index 0645c92b80358a366bc2a50d54c803ad3054f6c8..bec25b12c78c96f732f0b82a831c37f2ac0aa4af 100755
GIT binary patch
delta 23277
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
AA^-pY

delta 23020
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

diff --git a/package.json b/package.json
index 4e04928..6222ecd 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "@solid-primitives/map": "^0.4.13",
     "@solid-primitives/page-visibility": "^2.0.17",
     "@solid-primitives/resize-observer": "^2.0.26",
+    "@solid-primitives/rootless": "^1.4.5",
     "@solidjs/router": "^0.15.1",
     "@suid/icons-material": "^0.8.1",
     "@suid/material": "^0.18.0",
diff --git a/src/platform/MediaQuickview.css b/src/platform/MediaQuickview.css
new file mode 100644
index 0000000..b1ceb07
--- /dev/null
+++ b/src/platform/MediaQuickview.css
@@ -0,0 +1,69 @@
+.MediaQuickview__root {
+  display: contents;
+}
+
+.MediaQuickview {
+  border: none;
+  position: fixed;
+  width: 100vw;
+  width: 100dvw;
+  height: 100vh;
+  height: 100dvh;
+  max-width: 100vw;
+  max-height: 100vh;
+  contain: content;
+  padding: 0;
+
+  &::backdrop {
+    background: none;
+  }
+
+  >.Scaffold>.topbar {
+    position: fixed;
+    left: 0;
+    right: 0;
+
+    >* {
+      background-color: var(--tutu-color-surface);
+      color: var(--tutu-color-on-surface);
+    }
+  }
+
+  >.Scaffold>.pages {
+    display: grid;
+    grid-auto-flow: column;
+    grid-auto-columns: 100%;
+    height: 100%;
+    width: 100%;
+    overflow: auto hidden;
+    scroll-snap-type: x mandatory;
+    scroll-snap-align: center;
+    scroll-snap-stop: always;
+
+
+    >.page {
+      width: 100%;
+      height: 100%;
+      max-width: 100vw;
+      max-height: 100vh;
+      contain: layout style size paint;
+
+      >* {
+        display: block;
+        width: 100%;
+        height: 100%;
+
+        object-fit: contain;
+        object-position: center;
+      }
+    }
+  }
+
+  &.lightout {
+    >.Scaffold {
+      >.topbar {
+        visibility: hidden;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/platform/MediaQuickview.tsx b/src/platform/MediaQuickview.tsx
new file mode 100644
index 0000000..2a9d7e1
--- /dev/null
+++ b/src/platform/MediaQuickview.tsx
@@ -0,0 +1,179 @@
+import { createCallback, createSubRoot } from "@solid-primitives/rootless";
+import {
+  createRenderEffect,
+  createSignal,
+  Index,
+  Match,
+  onCleanup,
+  onMount,
+  Switch,
+  type Component,
+} from "solid-js";
+import { render } from "solid-js/web";
+import Scaffold from "~material/Scaffold";
+import { isPointNotInRect } from "./dom";
+import "./MediaQuickview.css";
+import AppTopBar from "~material/AppTopBar";
+import { IconButton } from "@suid/material";
+import { Close } from "@suid/icons-material";
+
+function renderIsolateMediaQuickview(
+  each: QuickviewMedia[],
+  index: number,
+  transitionFrom?: Element,
+) {
+  createSubRoot((disposeAll) => {
+    let container: HTMLDivElement;
+
+    createRenderEffect(() => {
+      container = document.createElement("div");
+      container.setAttribute("role", "presentation");
+      container.classList.add("MediaQuickview__root");
+      document.querySelector("body")!.appendChild(container);
+
+      onCleanup(() => container.remove());
+
+      const dispose = render(() => {
+        return (
+          <MediaQuickview
+            each={each}
+            defaultIndex={index}
+            transitonFrom={transitionFrom}
+            onClose={disposeAll}
+          />
+        );
+      }, container);
+
+      onCleanup(dispose);
+    });
+  });
+}
+
+export function createMediaQuickview() {
+  return createCallback(renderIsolateMediaQuickview);
+}
+
+function ImagePage(props: { src: string; alt?: string }) {
+  const [scale, setScale] = createSignal(1);
+  const [offsetX, setOffsetX] = createSignal(0);
+  const [offsetY, setOffsetY] = createSignal(0);
+
+  const onWheel = (event: WheelEvent & { currentTarget: HTMLElement }) => {
+    // This is a de-facto standard for scaling:
+    // Browsers will simulate ctrl + wheel for two point scaling gesture.
+    if (event.ctrlKey) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      const offset = event.deltaY;
+
+      setScale((x) => x + offset / event.currentTarget.clientHeight);
+    }
+  };
+
+  return (
+    <img
+      src={props.src}
+      alt={props.alt}
+      onWheel={onWheel}
+      style={{
+        transform: `scale(${scale()}) translateX(${offsetX()}) translateY(${offsetY()})`,
+      }}
+      onLoad={({ currentTarget }) => {
+        const { top, left, width, height } =
+          currentTarget.getBoundingClientRect();
+      }}
+    ></img>
+  );
+}
+
+export type QuickviewMedia = {
+  cat: "image" | "video" | "gifv" | "audio" | "unknown";
+  src: string;
+  alt?: string;
+};
+
+export type MediaQuickviewProps = {
+  each: QuickviewMedia[];
+  defaultIndex: number;
+  transitonFrom?: Element;
+
+  onClose?(): void;
+};
+
+const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
+  let root: HTMLDialogElement;
+  const [lightOut, setLightOut] = createSignal(false);
+
+  function onDialogClick(
+    event: MouseEvent & { currentTarget: HTMLDialogElement },
+  ) {
+    event.stopPropagation();
+
+    if (
+      isPointNotInRect(
+        event.currentTarget.getBoundingClientRect(),
+        event.clientX,
+        event.clientY,
+      )
+    ) {
+      event.currentTarget.close();
+    } else {
+      setLightOut((x) => !x);
+    }
+  }
+
+  return (
+    <dialog
+      ref={(e) => {
+        root = e;
+        onMount(() => {
+          e.showModal();
+        });
+      }}
+      class="MediaQuickview"
+      classList={{ lightout: lightOut() }}
+      onClose={props.onClose}
+      onCancel={props.onClose}
+      onClick={onDialogClick}
+    >
+      <Scaffold
+        topbar={
+          <AppTopBar>
+            <IconButton color="inherit" onClick={(e) => root.close()}>
+              <Close />
+            </IconButton>
+          </AppTopBar>
+        }
+      >
+        <div
+          ref={(e) => {
+            onMount(() => {
+              e.children.item(props.defaultIndex)!.scrollIntoView({
+                behavior: "instant",
+                inline: "center",
+              });
+            });
+          }}
+          class="pages"
+        >
+          <Index each={props.each}>
+            {(item, index) => {
+              return (
+                <section class="page">
+                  <Switch>
+                    <Match when={item().cat === "image"}>
+                      <ImagePage src={item().src} alt={item().alt} />
+                    </Match>
+                  </Switch>
+                </section>
+              );
+            }}
+          </Index>
+        </div>
+      </Scaffold>
+    </dialog>
+  );
+};
+
+export default MediaQuickview;
diff --git a/src/timelines/toots/MediaAttachmentGrid.tsx b/src/timelines/toots/MediaAttachmentGrid.tsx
index c9d72fc..6291a86 100644
--- a/src/timelines/toots/MediaAttachmentGrid.tsx
+++ b/src/timelines/toots/MediaAttachmentGrid.tsx
@@ -5,13 +5,9 @@ import {
   Match,
   Switch,
   createMemo,
-  createRenderEffect,
   createSignal,
-  onCleanup,
   untrack,
 } from "solid-js";
-import MediaViewer from "../MediaViewer";
-import { render } from "solid-js/web";
 import {
   createElementSize,
   useWindowSize,
@@ -24,6 +20,7 @@ import cardStyle from "~material/cards.module.css";
 import { Preview } from "@suid/icons-material";
 import { IconButton } from "@suid/material";
 import Masonry from "~platform/Masonry";
+import { createMediaQuickview } from "~platform/MediaQuickview";
 
 type ElementSize = { width: number; height: number };
 
@@ -58,39 +55,23 @@ const MediaAttachmentGrid: Component<{
   sensitive?: boolean;
 }> = (props) => {
   const [rootRef, setRootRef] = createSignal<HTMLElement>();
-  const [viewerIndex, setViewerIndex] = createSignal<number>();
-  const viewerOpened = () => typeof viewerIndex() !== "undefined";
   const settings = useStore($settings);
   const windowSize = useWindowSize();
   const [reveal, setReveal] = createSignal([] as number[]);
 
-  createRenderEffect(() => {
-    const vidx = viewerIndex();
-    if (typeof vidx === "undefined") return;
-    const container = document.createElement("div");
-    container.setAttribute("role", "presentation");
-    document.body.appendChild(container);
-    const dispose = render(() => {
-      onCleanup(() => {
-        document.body.removeChild(container);
-      });
-
-      return (
-        <MediaViewer
-          show={viewerOpened()}
-          index={viewerIndex() || 0}
-          onIndexUpdated={setViewerIndex}
-          media={props.attachments}
-          onClose={() => setViewerIndex()}
-        />
-      );
-    }, container);
-
-    onCleanup(dispose);
-  });
+  const openMediaQuickview = createMediaQuickview();
 
   const openViewerFor = (index: number) => {
-    setViewerIndex(index);
+    openMediaQuickview(
+      props.attachments.map((item) => {
+        return {
+          cat: item.type,
+          src: item.url as string,
+          alt: item.description || undefined,
+        };
+      }),
+      index,
+    );
   };
 
   const columnCount = () => {