From d653e8e75d797d7349189f112bd82ea1d45a99c9 Mon Sep 17 00:00:00 2001 From: Cesar Alcazar Date: Tue, 20 Oct 2026 09:59:38 -0600 Subject: [PATCH 1/3] 9uv0f8oll: Fit mobile videos to the screen without cropping --- public/dev/landscape.mp4 | Bin 0 -> 38262 bytes public/dev/portrait.mp4 | Bin 0 -> 45006 bytes src/components/MobileFeed.tsx | 37 +++++++++++++++++++++++- src/dev/FeedPreview.tsx | 9 +++--- src/dev/fixtures.ts | 51 ++++++++++++++++++++++++++++++---- src/styles/mobile.css | 16 +++++++++++ tests/mobile-feed.test.ts | 11 ++++++++ 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 public/dev/landscape.mp4 create mode 100644 public/dev/portrait.mp4 diff --git a/public/dev/landscape.mp4 b/public/dev/landscape.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..abc4d4992431574333cebf1e7ffd8553c7dfc693 GIT binary patch literal 38262 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

OUaU z*}#%bzp0R-2)=OTa_t2tM6EzjR;P9L0&ecog)b2)-)`_X|CGzwR3X}Is|eLO;;UX@ zj|E24aqy*S_p(lYJiq0AB163cqvVw@HPMS%@a0_tQ_ZAd3~7U9VvP6MPmrxncufex zs0V1_Og_|AAv|U$-$KjMcl+a3wMCXp_e;N>xkZ@;m>}czk7dqrOjqE<&gm!fg|4$F z7J=|o(|A#%C+~fTFN(&wNKi^w%x2C&=e+@CZjKN2o)PJ_i!~-Q`h8-HxL%5fiapdy z+u0yUp{_;k4!>sk>!`Psi7@e-t_74M8&q7p621Kf+U!2{tA9GXh2?Dg@vIQ>=vB#o zKGL8}bm@=1aYO3szf2et1{|-(xtqA5E#RSMElz99ZDEZ>I7E+&Er%e_&l(CHuo8wb{I*t=}Zqkn}zZqE&A@q|M)?TiCcge(F zmS=&s9o$hd)(39X7reu{vaJc2?o-1R(`Hb0+r9qr2+$8BeO47E6B5k^aI+idw1`J# z2WmsfERodqz-z|p*#ufvBJr*QuNq2+?wN{`V}DD;LgDgPj`>i+=-FP1u$iRnn7K3q zq(XruEPtkx=y0e0nq5^-Rk^*42n5JZd5eu=u?aewpt%?F-c|(|Xa(vwRQ(4 z(fL_ht&)y;=HN#5CxHL}1d>6TnMtTYXqik1kAIdD-f{KC5Y{FT%bQyNko3O|KNNl9V>GnPU3zwW-P1Z% zOrWnP3W)flr60d;SJ6ju>tmmIWc%$0@?=a7E}xPzqyPWITmQg?Q1wWX{P_x5RsTb7 zwEZQ|<ytOlEldW+N;F zDSW037=W-!&~En~`m~G^Z>#=m+!rft$h1P_6YIx@YjWhKK`np3UQ}v}AulGQc`DJp zgm7m!06{e`{J=@TL)8cf%o4Z*%A$wYT+QpFl2}i2hf(^FLv8;c=<$JFZaNA5*}UU28o0MadBXEa#Q6xM$NJ-8ARI&ux<8QsB>n3^9mO^V_wXBzYvtbm3q&Rh1I!#RG+kj;S#H^ ztBktu*wGvvWaaEmEiOS7d?LHdn-@$eV+mUDqAs%&ZWi{EmyQ@n9_(W0MBjzc27c{R zyRW5qec#m7yHbyCp!LN!mF%nsukseZ}VGhZ@4ivcG);XHsQ;HCQ#4wkSc><NsUQ!@mt z4DkU41S_bTd3X$Yc}Gt{{j4>D(Y@{}F_K0Bnsf-5TkRE3fp|DVT9R6v4v9qtCci1p z`S~BsEb0hY;`=-;X~1BkH%BBbEN74<{NYoRFoF6KR})cWpukBiBpRhBLI?sOtbggH zBVLadZly_o_Wk4t zf25hZ~_*)Bs+T1>yVQu`_MJ_$ALmuH-{9g_>`jnMaZ(MdsVDQ z1FTRS7$F>y?X2BYrRR(*`2y00S3h|j>I);{6v267@a$4O=@ktN|KJR5qLx+`w9N)=NS@?43`DpFdlXbU?)`RQwqQIPHmYNbvM`8sAX64 zmr=E7MUY#oq$9A64|EJ+;CFf(Qq|r=2x6GaS2byL4VUcTvgzIae~1P@8%RHKKJ~Mr zsAsijJdSKP=ZE*K!Df%fdHW~9eW1@a#K;Ukq=MVyPJt9U`?C3sp1hZ!000MPL7Kct z;R;e^FeASz*>_L@m!>ykTvq3f^}|&EsP44{6t9?uc|2ql8`PEg=bsnf*}#2seDNFV z(nvaNmh()C=Ie;li{|bU>mL^dt?d*!J2sLZl%O>r;ME|}1rv{$6hmLR{#%!e|Kx%l zL*F^M4Cchs7W!pF&4+4K%g;%=F-d(A+%FGHlVO64I*=dzg-`AsPlr&9ovd`M1k!{O ztX=~srgO>3Nft9I7%sox6@;WQ&o}Pv^dg%W%?wlWb91+Nj^1o;>tiP!!VlZkiQU7< z|E9xO=c4JULs{_=P5*Rvb#6@W&ArFxYn+ ze+p1x?gO^$qq17p<#)uW(h?&vhQ-?p8c7^5V9Cjw9T(MV-A9X5D-{&w2m@Y`2+iLI zIi>~Vh2cRv*+Pr=1X9e>iIe3;*y^O?`3-@?!)?CaO-o#vaK)|AXKVu=T*fjls@Q$K zSt2|YaLe`jtA0*|DrtOr3=H&rKTg7o{+kq5Qi9{mD<`8qo=ZIcJWwJR5!!KvK@E0j z-CHz|sB;2+kMpVN6@fKOUmFd+y-Upj>)cj)}F zXDJomLsN5Wvdr^X=nn8+)Iwm$v~h^e1OYk`#qJSD`097Flopw4ZyG6^^;#8X*HR$l z6cPbS2#55xemgY8`B_T-a^uW!0iq_$7#8DXg5%9f(&(|>_eRPQ@X%wl%NNa5o&O*? zbXtfcE7gr^R-U2`WvhhR#8wO6=jT!E^;}*EkEku-O&u1nk)5VA4xE1qhhSrmOubMu z6f>m=t%w3G3G31+M#_t}%Bt0KoR3~eoA<9O4>&!#<4^j^;C1jq{d?Vn7B}^4t2{2P zWU@>Rsomoh%_)e?v!0|EYj>=vDXgga7&(9<<4Ek5rQDGhc|H7^s$TCVzZzLQ2olIE z&^~w@3sI3wCi%WfvpOK7%#%gfUG!NB4InpIh8lbbTniv7W9duVkW-$B)<}+!hZ%s; zAm@JmBoM4@BCt6}XJ|Fh+dirqU$Q^G?^20q-rc@&DW1UvnnnGkQCcpOT5`esdX8E^p=3O<5%FH@1u4 zO^MiBdNY{Da01C;KF2mubnbFQ!a29#?}#S5M;!)blmj>)UmDkryi{ z?}7?&mn*b)X!_H**`t7~A4lZCq+(O>8|2M&={*L$sz0|KcWdy2R{}f1^@iAu@YfGU z-ivG`d_|i;2mhs+H{?s}vu5OIj3pbak<#3GdNcC<`w9mX-tDj_YC zP?-J|EpTG>v1~ zCI@}4F8Cgk#OY@b4BKm8d`1JjxxNpdWe54m&e3?%?;n(ZcKAM^jOBpHCCgj3);Ie+W0Mx!^-R(lZ}I9Ro8u^~EB) zY9tN1D?-K+tEO_$M6f^4Mjq1=0pUDKj2O1)*v_o!oHewp9v&@#HiwV5(a(}VNz z!H5-#2(&m^HDtU048N$D{TB-6@<(u}suH5Op^o(Ab?enQ^~FmCPT^0HZe}JkjQO*OZaf2A`U#{8Fc0SBOXS0c+ zrwg!(qb`mPS)X9J<$v}PLT5nEqn5PSrAux&q&Rn1YX_Ej%l^qnUEldLpU%HqVv}=j zNvt3D=4#w(%tBB3pfo41i6czn^|Lqe000CxL7v)0G)y7?*3^r8pFX_^$ZrzjC8taO zYVJ{9sx?&?aJ#drRM;P{VOT3Pq&jw8we;oWQV{Xqn!LYY{xMBfy7Lm=WL8SD~8SS*( z%JJ#q@GcfnQrv!Jw#J0e47`cHYhd|fRE}6xT+9I+KmMpRX$$iBb`>-?lT7{y)<72m zAN(4h%*`ce1Snyp^Pv54kx$co8VFID+-0}Wt`$Y5AXR*@0-h#cl(Qq17P3~0gS5Lw zL%4fQ*4W(ljHRmSd>G{muXwjAx>)0%*$dLd$^2t``qO!OY#>o#-s>hcSWPzL4 z56PXE&7|*&uG$^x;P&~0q_fKlK}{N+)W?o`t)gu zG@W)dDd%V``(z!U8G+(Y!#@+RqF)H5#gtW>=Yj$Zdm&}O`ppU?i(Z+2FV$5|7Vhro zQ|&n=_5pmalhvkiTB0#>MDH(uNBepLo0(O*P=LWb-5Xpr$$SOo$0AoXpkB1m(OTvK)N7_G zQX97ipqk1&;ijD_v{h%sq-+YGZOQFf2Qgtd-}sGUsW=-un#sC?%WO)C=K$K~1+;0J zL|D4+I4?CxT^kLl<%)*$xZOnQ)gS>m!H4$*KZ+g$q8+P!7 zlF7|ddL=%c!URK}Z)_mtO*CR92Z`D6n}#c}KM~}JIM`I8=??2u7Im}N4;ewG3bYX! z)QbcLml4O&aDYm4w#vSQCAvF1jgQa1&qrTvc_7?6!f2hw#hsL!AIPU?{iro@)>I$W zxeDK)3Tp9m%8>c1+f$*X$0P-XRG(kX)O4C`Fp10J!~_@k_z`J6RqrE%Z$bOD(d(0R z4wT2>XyJhvM33{ReQq!S00bEUp8IM-U($_4TW%EV`K!_f4szce{d*LmYb7q6`nEsS zKA8hjFmb&tfDRH*H}FgZP}ypcw+^PD7V0lZI2Zm{(*sIcMLNaOz3aTAR+A*#)g0{-d799}uxtNU{9(9@Vu)P);-Pb`(kw%oW{DsK5SMLsTLe z*?rwP#3|e9#2x%_&~L$HJH&~*%7S;m8oppIF|^!OjkE}4xHZN<>O9M`5hgF3SG$Ck zaH_syY~hUaWl?*rb{lM!bG^F%A|e=q&mhiLb=RrFHTnZj_7qkB_vbGk=Z<*j4%SSN z<0dGyzfH)4FEhFNMWQ@CrYckA=FtSktXPhvZ4upxL#H)idzuoPcD3?Wg82Ux7AMnP z$=~0rF#H=?3LcNWaf9ZyqmTLJ!P%GfWp9bv{D#=9+-DLMs<{{(q%aQ$Rp{f9950iI z3(-})ic6^?5b2=VBPW(zNWtIthY$j4aaLE4BiL?!ddB$DXw|p>q&v{Rp%ms4&=zlO4*zyZ*B%h#9^ z%Kw#PTqa%kMwIssdt3tnBt?U7kFG8y|EB?0Y?j?8T9z{Ak+6jRkL%?ZE)zG54P=v; zfJ5wDJHpJk<+m5_fXS2?{jPUgzvL^PE5GXkI_|y5pV6=uhN@1GaSDIHCO4N-4V=<+ zEng+fE|95$#g~2$rY{aVT&spd@J#P}EFVIf_8raiG-VV8%UZj0a`6&%M7lCc8j3Gv zN7$8@YD>!p6pyq6f1i+z2)^Nf!PuMa<<%}lGuIYrso?w${d@*3Vg3pwDFi+XUeT7x zDYlBf-*>1^!_Ib70+|}N&2m80kegG0YyrHgPt zHT{|tZqMo|(oOdd^&`n>;G|DSmwFgMwFW|$^qf=qcGyPj(IJ( zB8!kp*mk&<18(TvqyB zj};u@ykZ;$Ffr^>gj-fLl_s)lHu&a|xf6@fm^VKS*DGN63GZGg?rq@~tEDFdR}D)C zq+2h7fLu_C`!<3sxG<3H7sq=0(>?p>ww)c}000C8L7M$Zs6l9%ObAQ=q*Op;=6pYo zFt#*p<7kDq{$W0q+3kjm7-d82|H|IKsWa4J#q&;WOcSrgUttu*ZG=Bnv2DM32;RJLJT}E3{{C6M+HBm)=6`zAhuR*#46y4KfvYBrD_2)r z0b)|leccOZotKTqI`!sf9_xdBBy9yl3$}=-%6F=bn!HrM7C$lM|C5gTWIg-b3Blg= zBnT|oso+mn2e29yrfGP&ibg&~|CiETOS%5J=E?s1`QFwvSP+l+=8rMg6D&D+6eT2H zkr_TlR|6)-e7=>`_gL#%9_YOTOl7DtGVhb;9jB0gyNnYx!}p|}leXtdS%?K)q-Vm} z{~^&fNDfTFrN~Jvj%pKx*b)O>(vj?})3q*K>UGx*zMBo)rOUU)SD_oaQs>#JRlogw ze18v!1Y@^^=1<_D8AnPc4*reMeJ}vQ~&rU=$1 zV92DNiuJ8_?XzOb{y{wOTv>bwa!U5lO|?mlo~CPhZ1+sH*!vHMk1f3;^%az$Mp!61 zvWu81`eZ#ndIJ>YPg8exbBWSPL*4Nr0u1fO`gYHWSKWUaH}(S0`YAWWh+WSjcoq2_ z_yMl(?&BxcF@bBJ*i5Y&ei^XuUI=?FB{PZbgHNP>N>dK~+@O^fSCdVz5nXflZ4+I? zyX5LRu9@_X*Y`q6-j7rm@s-%SmkTYdQ;jo0n!&whW`zdk z7UDpt6AF8ZC9z(`2%nlA(Vz)xwPlc(11ZYLM|xhFKh@7y!=wcY#)ojYbQ%ZhNfCY z_o-Zcu2q>KArCs6ai-{LU!`)bq)C$#BZ>7bHwb|vtz^T_DqFpR+Djb6tSbQm{Xz?G zM*JOdm?##BB$dzXEi1!X-m~oweuwu&q;!eiCbu2Scvbko1Q{6eyOo5v;+_$=(Ekea X5-Of+bneW+wkPfZf;$IycXta8!GgoVg1ZNI5AN?9aC*vH?0wf}Nr{c(W$r9trH`Doynav*z77Dg5xMiyokfR%-j8!IRKzm)&Hao*oSlnZ1f1`Y(ndfx)W zGXv3zB@sL2z01#_GJSLd2}Xp1#gGIJ24jA*nedDL_2J>e8`aLx(GEmE2J-P>7&$ro zYnCA@J_i5Ma2%cfslW&Rupt7iwhxQ}O6;RDAMHQ>o5ufTi}x=%s7n9nG5$^e!+)?J ze*eS*(f`O%Kjfg*^&k2Fi~oPc{r}bfAN>=6ayI@LAgKK(XV857gCjvYkAF1(dV%C< z%%Hs7z`&qgLHSUEsvrvn1|tLFcR>6v5T6g?IY2lRgjYe>9)ycQSQWIw4?y?`^w5HM zF;E&$MP}9bs%i$40JLC zQ7vnef9Qvr3_ z2R0UFZU7sL2@8)Y4@e=+2r^)jSCWuoUnCIT#+0Aq6pTWdpokShzo$-%_R z%EA$ZxZSvc#!et$Y;P^V3>tzV(8JcoM1Ylro`nTqYUt>sZ|7)fVfSJ2uK@OT`nIN~ zjwVh546Fbra|e)zqW~*2z{=Lv($E~l>HkN_25_{pFa{0gKLTcejl(~k7+Y8yI(-bp z!p6zO!O9S11dl@oz+Zj56cw^8Ufj;OKHlPq7LkGhT8&d~EYZFJ%Y#Hg> zxr49;P=FPL4S|MsA88or8(A1Sesp5tVDe95t|k^{=1xW+ovodTjlP+!9Z35xp&iK8 z(!?DUTY!~=`QK0<^qCc4;Q%-qo7k8bJ39%mGk*-z!SG{F9ZVd}LGBL5`u{uK550r2 zfU$!qz#8LyA|0?=P z|Bg_AC1~Ew7;QiEh@UlZCr2r_O~y&)tN$%Mr@5NeRik@XoInm#;r^K_&4M-se%g@7pt`ujlhu1R5pI~*hA`l_n2Q0 z*e1X3$SB7R>&^e9P&l@R;VDQv9nLLeL9(+AKRY$KW+&6oSuGQ!XFtbUhOLIiuLXY1 z_WRqBko;LUuXU3;F=e0*C%!o%&wi7&NtT}gukbi?ALMbWE@J+PdhEh`!ZOxSN!dZI zrE&c_L+b?n9mKL#`Ss7_yeNrSKQxtT)I0J z#XLl5-smsKHxH`!o~}EP8%t8?rtF5 z7kY@0vp0yM9I>W2Lb<&cs+u$lfmJl$(vlB2Rpl{EGM=r9B*tW3k+@i*Z56(8;oUKbIA@kQijz&t zkkYbAR|)7PiVJnlL`X3ryeF5jcN*X4LKC&s&BO`7`r(;|;z|t}3Hh$!e&8-NlW{wh za*r|Ts8n&$uQ(0wG4q;w!b7T^44b<))pu$Po#(hMF%A<)YLB(Wh8W9IRLUGy@p$HW zexo$Z5Alv@IuPFpRj*(b5T|$<&*znDjW^+n#t}HKPQ5HFNyP=51L3S`IN>j0#tuEU z1Bc{iqu_VyrW7K4CLtg&~(Yf7f7nj|AtnOx7w=Xb-hu;w(afxo}Suq1(6wu|UgG zkbDZm&6L#tLH`WCu=wlQcdb?m4Fr*$%A%V6Iy6lwgipfBJGKZ;OC~yCo-ki<=#9eTjR;gDu9Vil%{;|^wzQ)HIsO`IE7kZHp#NB)ap?Q1^f#A!SNe+vK#|@U znDpG5?b12?cKaq@Jix;)`E4++&~+zY3;N^?W-20Q2bl_;w;bt5wY)Jp2^eg4CX zw$hL-s_z;%(|n}kxzH6{28VkAV(=dV`DLL@J8GULjk{<&oO~)jz?vr=BzoL+aF)=g zr5pN=$%!Q3BoXA_dwZ4w18kl1Zv191N11gozN8WB-9E5BwyRAx%4-j2V9PkmsFhKy zoTFYKZS}oNAI147Zch&+KTE{s?KLN@{vMGdoM$=TF8goW16)%~b)zvxGIr>TO6j`T zn}c$Wg}idi5Zeyn<0&}r4Fq5-US_C9<(HL!y7DxEzHMxAwV)oesf zt1vKx3kn>bu3$g|bEdG?DjVDRqnF-+rEqziPk&_la9c$%@7yOJ9LEI#UWN(a;j$Gc z6Tl~-wK-Ix);6*W>304%kJlfD(6 zU{;Oel-!DwkeXD6eS2PZB#zJ_EsS8*xlGdN27WV-o$9nt<;3S}1Z}#A6n!w|bMnql zTBy+2KZi)MgSUumSXU|$$L1lPOL$M=gyH)m)sMc#jJEN@$1R56s2KCW$%P=byutn~ zqU=PEnuq$KIh~AoCF(9IRbjKUlxVNptTLnK^|cq^GsKi=P%!N6ZC|3i19|GLr*T== z<0lrwweFX<6BUIZ+bdcXuQyL$e6^;6yJ`Nw5=j+-+$2Q>!ZIe-^Y^}Zhc>`(benNT+6?0_HM-)1PZw@`ZKW&CGEVbR}#g(2XxycMrL zmir?!g73P8;~sYBwVQffFj}vp+Q$5&j4d72qQo;bjUw2C^^XG8;{o9Lfq_3@`MSTug0hGK|0aqa$^7$FBksLNC z=2si~xZ?BZn4ydZo^jQJ8@_yr|f0-q`CM5l0t~IA}uq2_S=a%wm>P!dD{2a0xRQIsYzv zN7q)N8a8&y^^;X6J0X-*7Y|XwH4wF^FSKQks17b0n)_RlqKlKh#+Z)myuN_R7dj2? zG|UO*oP$fSb-|ICm2F!WfzZ--5nu2yA2tsl5yQ(@u`sO)Z{5o0 zevCg{Z;QP(!R`+;Wcb11f4KLp*Nv)Ti|8ZD&y-F{x8wLOx{n&jEB+_``tPG?>r}smmK+J z2ca^xl%6>6W-m7>T;^6`mhbKr5041>Mh${cQM*4cQ?@qiQJ-t^ZWpyb2Cl#<4k52# zrQJ&YxK?wX2pstWjC`;(b7`)?D0fJfH5*>?+Nk zBcS4dSlF<7)^80vCpYcDbmVj2TAzIM=FiI`rjKrjd*g-PCNe|o}!K#)=gDcU~nbRtFGQ5Yt%!!OslwKn^ffN}j zeG*@Qh+jtk(xGTAm?t!+457QD+sVibZbUoB4IVwE*8q=aX_HP>fv;B>B!LGQmOHb- zMoMZxiUA#dIZB83D~UY}!G+|UzWE?oq4?m1k&DAc$XHk#R+hLh+L(J`_u{rA`lXVI zg^A=`QNa|H4iSewaiPdbP?vLq3{h^qnPWB2cW6%H&fGqAUQs9xss&dV%f2a-HC>u* zR-OymFJ?u_-%)P67A*8e*?g!swpn%0et0FWyWto;@-0fAW9dkyy3Xg$ig8XB@Kq`d_MCeBl-pnoA#Yn-_l)Yvd z$Mkx|g51Y|c27rN*rxUCa1aL5#Ioz>-|~~|)#qND`#gBAB|8WvZqx6jSju2IJ0%%; zTr9+Q&SsjAeM{`d$Q3u%-<#q`dsCaTJK#g<%g{;6OXzFD-zI&CV2gItBq!L?0uOON z2^Hl;=bY&2cjb0}l~9(OpLbuVx<{43D3l-^h>VCh;{X#|$cQk5bf&s}EvM1I-F8l1 z2w@a;u*vsxA=?R~C7Q4+Dl7T@VEfZ;%~nx~=&!4rJl9%4aI&}&io~!{YvQ+Je?{l{ zCgHMp3U(p}q52>b{;{WR1o|94{~tGw^=G~~UHWq+nttIcE&_qf&kWpDZ0UpWuENbz zke6*8ZM8GQRpFJcRvuD$;(HFd(|eO%xx~Eywd4lZ^=5Y7`g@Ps6u!879*W-DlEpvU zCH6H4pM0@|Q(N8y9X5>WLP`0%f6emA6DkgA{*IZ4Kn{zE~Us`o>SkC2iT`>;Q+$V ziuQ>=hG21H!E&`QR00oA7xi_U+ADa_s@8>Fmg84O4K$&OJoqqTzxyjZ|lYu!K$O!$I06Yt{jHlan+^HVt21B zx!rw>a{WU!rgM`}LyLMg*_8mRG0)IUCs2Rc@q|iqt8uJ;BZ1eqS(ZC7>INl|4!lNX znIiN{O_5I>b6v!f=@u-SllkMJp~xRxT-pf28XZ z{DIYMuS0i2)y^FpT_luE0(+h*CtZ=Y^YXYEATcbi%yqwDEkB?TjSprQCx zEgMK&Hf#|VGlzfo4Oh%~U1{R7*Y&j=mokdkj~u>0_|6=X&m{?dP>uXk^Ku@ld2-ob zaoDJ5b~_J%f7Wi#NhlcSO(`W=gpQ~=qm66bwMBd3UBo%lCS>mB?_|v^9LvkT&>e)l z2dUZ1E{Ww&sr`2LJ2dmS6Q*YaHpC9S4cBUa3x8W9FZQXSZG6K)*gC|PHFsu}XzR-L z-gbg2&bs!-+`{*XTrB+UOFHR@AKfeyt^Dez{g#SZJkHC-+=T!0*Mt{)F!#zVk?91Px? zCSl4svRL{wFp>?wuPoH)*G^;!L*cC3Ngb?E0EZJR8adATDLbig%Y0PR+L~!aD7*2; z;`8ra?LsHash;kcwCB5L*Z$S`O(I}W7>|y0#+-yf=eJYt?Ma5-vSq5HA5v|DwI?1~g>Fa7`1x(0Vd0kfJ;`)qOew_7yh{#rQT;8163-{0b zZK8SKpY{ViR+fQ`>oH;J7e$s1eUMVCIqHJVQF$mC3z|{O22I%NrHFsaI=XOd3ugzt z6+cyzlR_4B7k~Xxh(qr-*2j?zO^3&|SDUKnB_gVNB{a1wnI zHPgFAB_0F&>)b)OX%c->ikEWW;@__3Vm$y%fcazVqSqB|+o}))rOWsBw3SC=+#jQY zbC1iaA@~kth6Ql*VQqqvM(oK#>!@G+@5VU#logEm@fmPC0y(FRH?qbQ6AIR4vA`Q@ zx@d6Mk_otRUe5tYmGXq`4T!e~%{QJVb0nt|YDlz)KN_U&;C{c@D9<3slUk-)z0oQD zd`NaehsHN>LUI*cD(5!vZW=#eOU82J6Gj4xCpQ>qMIn9@?Dt7ZZ6 zP<08i|KP1OQ*@DD-3ow-xM@h_R9{zwDN0srxnrZu=&Zhe2$4PL!V9bVZM5ShjB~|TS0T)lDg<&k-Qdx-p6IQcAH@#DD8*qr0c^tJaonkP z66{&PFA_6Xh(uJjIfgDmxR!pj>-HL@=rPU2+`1a6>R((00?H+5)X` zD(}j^46+lqD8Y_5o1wG925OEx;jdt+QICq(Jol5OA#(qHH$L31BTRP7n`|{|99>ab zq>K9O#WMRrY$!0MMx`;->OE;p%bxWOnWgsrT7nCqdd6?=PKuoddL zj4txKD*7en7t&>6L_}s6vh8U`f-Zk0`@u#2`IpC5M%u{u#tC(TtP|I)g>UMHaX`9U z=f7AQPc^5%AP8_+e&q~Lq3p0VRYshewKDCPxjTg#2`2~O1F0{8&b`enM`L4A%2!QK zgj&ru*bbI2QDQX&RhJyx)BD^8!N!OfXHkEX1npg88y7LD=t&CRYX;fIW;NN02aq)& z8YIii>Zpi`zN8i&-mI2uwC($%h-e41D#F0;z|h!*@%PKCfO9Gh4WjcnT41L*QoJ*hQL@3kSXi1}#3V@k=$SFXs4)@hZ@9!fHt!23q1pn0yZHkCj|O{nha! zqnL&ein}cycTZe&mxE;rH!SRWT7wKBTg8&v8vCe2V%?_qk5q}5an8Y%%M&l zW5J!S@^#xY;>tD3Oq~BOP5&|)TxQR29_>W-y%Z0AJ0TkMlHeTHjm{0S{jMVXc#b?T ztZO)IiD4Bx@fM>E-^*y>$cngdtP12;XL@RkBT7FKw9NezA!Szr)uA~aAX3?H8xuKK zl3osP*cc|$_`Hp<=K89#*wQG4pEK&SHM$otDT+W?e$od^T5yumG#i450^hw7hq8=k z?Ihgqm}@7@wf0i&Gkodc-Ucwr8b~qEM{ObR9y3y~x|G;#!4=%1(79NmZjJU_4z}Ak zYV(Z_3b$%|cC#gy{6W+R&H(6$t)IiE?iTPrh>SU*NcN!05g1Z6m?^SzTAgDLi5r`S z5S0>tIzaP?B-7d#yl*XwDXMT{qk|O*OPshUoJF9VN(%Nfu-?=G6;LTx_zrPl`UM^Mu06{zYVXTx!zn)VDm~7I{TP)MGl>4T3K{ z%IJQ;$Q!g0XDt@~az-uq_aiHXw@cc}!g#NG^pN~7CO`NW8U?6=RG!znGMQ1T-iz|z zg%VGo|KK@}#Jgu+l*O#u>mavcbhIAbK&q?K^5I>Us8DJ5Y~oC1Ei(?G^)zesHOw}c zt&RA)bsXrwA2*GFkw2=83pF0IxUMf-$DrUU;)t_1wK?R<;8OHCaR@rOH+xjcy1BM;oCtX`Hy7BzgcshVQdg;Bws8Bw`)no^yl5)s_m`rL)Jrs-ja*qDvbk;v5{tR|hpOchTf$+; zACcAPN8Y9>3qilk7x4M`L^|*VY$+2-VAbs6q7uK`(8@8TuBzhecn%R=PGz0>V%kw$ zi*tmB1X#(Wm7bD|sMNMEo(x*dwA{50mL}t0`}1#*ao&Wy!%M>6?k&Su1(TrExS|6_ zu*!!ZR^v-JdOTSO2al()xBPBKNIMt4{7bRXR3JOe3v^ zQiJ>1ZOGS|lV|tjV5l5|Rfq5*6057b{amMx__(n|3Z7-?B&vV6-?7F$z95>=XXzq~ zYnEH>_Rg82a&BM)DP*B}g=jBFTXt<#GZI+DpZ@tTRN8<+JBqw^WZ-!JCDMt2B2uqf z9Jhe_qe+uY$NQm;@Rd51~`yw{&)XR+-{9%mz zs?T<=d#eIRZ&_0zzU{TtN9{UkcBTy(*cMCCFh_{4D|E3wd#T-j#Sx-P4<|^@2cBGC z_>1PdwzwIz#Ts`1ls%xne2ES!eu|K2-@eadK-4vMGNy`5sV)yZyzOPhUQIk^ng{w; zC|g5nQ#PLNd}X4isUIrD?K19x;ob3Pf!9RuCj_sxIrCM!D9=E%UvU`f%gz1S(q^04 zB{=ledYMNCU3lty*7_P`_qCNyv5x7RBsIyWU)SIK9c;L6*X!`Ux8croyGz?eUCho` zt~9_8(FQ16Uawh;Y-SV7>!GiGYp0&JDu;R|cWPXXYW2bC{;KhoM8s6d!O<@uT%U_w^&rYgv*B~Nu(YEh6%hE5$wgT%Z*c#T z#4l>bIhu&(kpyBSgll1G+(F>eU+p!{O1V_M-{H;46~2nh1|8L`LXKL2VU+m*YlpO1 z9W~{X2e>x3rg3*>9Mhs6YJmiPfsF`|Px^bWEWFbME!sJ0%e?#016K4=tpH!X1dfma zNQvy9(WSY7**Fpzroq5S@50h+E_QpKw0?dq_w57}w~W0&j+7S0LH8GxiF#oeUb*JQ zSa!03M+j`1HQA)EkT9m>|79X4)h?+3YZ6pM8>HYtB9 zLS^fCwSMA>V+Dljt){o@7IH8!2I8}sb!)Gj4}rwM`f`q|)&GN0vfrBOo z+CxpL`HBhk2h$lU?=33GK65k>(*j2LFvedSP&rm4$FgQjygHB;bD&wqOq6={aAj~V zyKms~#f~yH3Imzn8mAvFBvbpM+Zd2G6A_jE_DR4#lbB=Ji}TE%`>1?XdV&!fYQr?F z>b;MPVOyn36wH`rM-P5p8mb&Oob7+2g${orz(69}lqh9l^+9kX9rt!aNfms;xVKsT z!=<*Q_Yhv`OKqL^H_C{~iFmE38;EbOZ=Fmf6&DYB$l{Ypqc3=T1hIBTTRsx6 z>SA*ZsRWBY`Uo7<8lx`r{-0@vjOFu2Yez=pug3qJH|hCoKW7KLzp`bt=Nl1>F<3|k zF#a%%%q9I}d1rG6Lpb2jqvV*`3tv>d<B2iZX@Y9<(&dS12Ef5@s=5 zaAgjQ;J>=EGj(2(SRT(NXQWon_LCR?7LgWTF~6Wx7QWLA4Tqo%u0Jni{X&BN+!>TC zd*DGGbLJf1gwLS;3F2q?T)Bd%b?j+k-W4vIz=RI+cEoV%$&5cyI=q0hWXgkysQJWs zc+WaS)l`2Ez9(XABuu=FLS0WK+q*?f0!m zU^x)tLgdEbT9`f8HTlV zuf~SHQ{W+8G@6vCdwKPPE(l79q?yQ+h?u9!A-J=APJvT!=M zsU4u2(e(J<{<(&Woyum;AR`->T1RhTpuerHDe=BOXuf~??45pVL$BAG=dZ-bm)Pwi zkrSBoGgAJPSN8Iz-(cw%^bDo9kKLcIrms6^+L9NxO94KYcu;Z)Bk%Gg+l5Uz0`XT< zm4(YQPpa0TE=G6uR~`jDa`KNVE?n=A?mrLGX{{)bo>KVsTyl{ZeA&Ju>@-wmhOj^p zf9C0yJb%fXQMj%#q?)}zynx+X$n}95$CP3jV*XlKen^%8;lU$1?n>e|48#+XU2aOu zaJ_J~(l;$T@J|&;x6J7e!ZL>@p7=7Mft$yXJ@yK*5w2m|v2KKB&c%Yfjg6JB3+QOa zvQH`wA#n{KW>=;jhqXmW;_gmEQWP8M?s-OCyxv_V%^?;CST-jX7{|0AIBIr3G$aX6 z)7chaVBUR>uw&3q%iv3blUKv#$Z-BUVII)5!PFp;p=jYn=4C z14bxKviAxN$Raa&l3wK&6}NPU0^A1zHS5kgP8E#scvJBm`J>B0H%DX7Y?6=8DVwnO zd3xUiSc>z-9l}YOUrW!f&-t^y=uE6=P0E7C}!H{t`r z-7asm;`qFsNNKdbH!GgefNm2^UxqtO4FV%L!|lsdutTSbojs{6{T(cR_wpLKE?0u?-X<-n=9NhlHXf*L`ETa(Y~q^wCDjoS6HoF=!KkPSMTN?TEajhF~p^BaAH||<&uWt!4FF92ccO0sz;=G?Ni6Ih~Nx+h1Zg;u*-w2mxO(z zh!{>R^S@S7$Kfx*-4U1`LZl9c*e;`+^^4$Y%*f9#lBGm6F|Y+ioq?K{KoW_>+cFivbBX{V&PTeN31_ik52ldzYNs} z@};QfLmG4`4NT(VnyhPi!oiU+}Dl+H8nvf z>G=0+X^`G}C*+t?l{zb=wjtV>{mUE)os)wNGj$-i6OHwRI2{9DMpd}dw_$fSptTWn zQNXK;i1%^tZN^yePLYyKj2NaY#GoQGI!)PDoQx?Rda$CDmv9shu?ptp|0 zz+=ZwUNG1ofxt~H6Na{YTj?=J=Y>i%h=0@Rp{%>eUk`3V`?Rrw+SrHV&ya_BWd8=v zT!7}35cj-VEl^d*qHowQ4s>9)A(9prAl>T?>Mw!9E37|3YXJ-l!Qj}p5oV6-di6ViAnS%VFY?AdnqcArMO=*TOa!Fi zf_#|>iqD1ymt(Lc`2gt?a|wCu?E?SqMA?*Zc25>-=IF9(tu#FD1;@{Y(v5Pon~lpk z=KyTxJ~M%9ny5~+bFNPT2nJ?5tT?cjJ4Jdwl&nHa%^an)M}-eI(b|4C#_%)51e{6* zb=Jy?P?0Z15#deI=tBP4nIciNjA-CeU3wETkJ2Ws`QdsIt*|l#JAjh58w$%^`ez^L zKJ;tDvVr>fml|q%IdmhcaOqdUTB_Ca_c!*Qb}#3wmYbo;C~@5qXWBSY4y~@aC+}iW z;e(3qWTI(#Yrwf;q-{MB&Mv%avDbzi?a3YV;8SI(fd*s=460&$dam9#mVRg$)t%&cvV#ebt{x2Z;Gj z&H=+bH*71DA%3#m+i69&w2M^0TZ8L5eWGcz9LTpg@04-r3b5E}GJY>^MG#_mghu^WE zyV735Tk1=STLit5ZHCqgjt`J6f#zXF)=puLEEJcDNKP)|t^G?&d*U+g(?i4ReVf%R z2W`Z{z!dHE=-7)XQ;^Wtqc7j0ZgFW5j)$10NTLsn^6pL`7r*rUVQQ&qKX#26RbvN= zih7&QD?!6YvzrxYUq9H49SR>Id;H#6M6?S8Xl=_ZoJ^Q+2#jHMleT#6weXpYC=Hct z;*2-`-aV54Wj)HL*;`6Ed1JKT8+_-p^mLQillhw~utF1EZ zK7~6jg3Bw?ccUWVuL(TPVMm*UZIZ6zaS8QDWMbaAMcaPy;ecnANkTi+?Gn{T_~3= zQDf&uDv9FX-(E-kOEG$m4Y1kB-ESs&5%i996r4b7qao15j`%J z!nS3VUevl|Vu2i9xD;i;+jBG~_2rW>9Zs66$lIx>=L{ ze1l9ca3)sxK{!kPU}7XoeLT@1dI$#g<;A*NBdcOudN*2?GY>yzv4E>Nw``+gwE!P{^Dm8w53H5 zvkwo8@4FM`|I2^&o=-llX;Ohd_Y_F4V0~;-d9<~1cE9uK{e4CN0ZyYktKPX?bmKXq z%Ii!xDM$cZs!yp?>5prO$7_-zd&6%|On`|eY~pKMopypkclIYcD3`UT7zie!?~bp? zU)h5QMEvJcbLj(o1nidzi`4;N_35FvPYPnxJgcl)WxT(e;1A76B``@f<1E5@u0 zmA{1S;-RFaCX+~uY=X7(&)UfY6cv2z-5WcP?0GYRoJmdc+uN2>?wbvtNCA?l}dBXIou@B!;Rks+Kr3YA&5iQafZ~u;?w9CTiNl}4Zrhf31o7G=I zXDHY$cMO;&j{q#Csdy*dl3!o0wtB&rbbb$k+v&3oqWZ=nYr$gVP{L$~5YpwwY2pp?1wH_mKan-9)4$gTV7F@BvXE}lYe#xgsQP|jjpezkyl=Ask`%WM5T z>id^f*M`1S7O_Sk%Fm$m7Y)?bpZY;-UA4L%yhb&(W*86twi?*HChi>gO^0_li*vZ2 zE?Tmle>A1wMW8I^MI%;cCX-y8?>h{JN6Dsk;Qs7?-%Wmwo5hk_22$&mkFAIC1{oB0 z{esK1$cP1dz>I&Brns?EXuivEoJKtM$F-Cba9DrmHt8>G&J^+r27Il(uw(TDn1%iZ zpBtT~{1q2@vdlb%rD=^a`%^+^pCHE8sgkRcjgHIGt z9Yh7(9(8aENJ{J;S zI1+3mS8eandkM}=4%kHLrjIF2+^-79TGug0DyXhzcxJjC`%lAwZ*@pJO zr#S%>#Tz0eM0L%(f)|%mmR3=#FT#?hy$AXs=CF8pE=()os(>Siryu7t-q_D0YLVwvVJn32+)$B($t6jIU z^~xs-%@ye(!^-U^KV@FY1u|6`ujjZ>JN+91P@8PIk0n!v)f1*D(*YyM8YQKD|#pPZiRn z27`}dxXkR<21Qb5_E7?%Zpbqq93TL9s{1ElGz#y3v+w00`ZOBJxH2(5 zUeTnPVNQiRazZ3;W~sQ3rs9OF=S}`Cfv+o0fUF9+&XvCjph!1B7-zgJB`t!^)NOxQ zlwae75dx$Tc9SJA6z6>f#N}a7N3OWcPNq` zbKvz?uVpa^^k9sl>9;ERW?aXqiM)rp*FWV3paV!2k+ji&9YCVI+s1ZL7DX42$_CWE zsMd~&inO~QC{P9Uw}1ZTaH<-In8q6+8NC08Zo)@pI|~0`Jf?Cm_mS@GmQJea5MP}A zb|Xy_f|U2@?>@z&g`u~0S3@9eW2IOksi4mxJ&I+L$4|A)^E7Gnw!izbk+4!y*`&4| zxHm2OopKEM&)PdW$(ybECFvNvtB1_fE$9}n@3mr z9QWu+aT?*mY3;<~uP5H<6zVg!RoNMg7ySD=N$d|>s$Q3@68N_B9}E)C;6(_!!gFEU z!lDU;4!OooO2;-^!gu@^l2O0bAo&VY`ycy@rE9Mzm*3dY8%o!s-CaUSrX2QG)JCJN z3=Ry@XAW3f40B+03-+$~v1yCI;F$eUDav;UJuu|0%$ml%ppnePG>BIXj+jvu5@4N{ z^EXv={8BSRde;nCCcjI0LgPt@qt~ohq764m|5D5KRLWRfyvE^UwDGx!scKW#eh*s- z>~Z49EZ`%#x)8{U;6{VxrY*DgT5_@}ljno+#(bnXqh;3q8kq^9<+ z&QneCwM-zgvF_rQpKwhZHGtNXg6ZiYYf-(`zh5|I&i+_9K#sqp|87a{3%6}$-385t(lxXTpNw$x zcJNTS1_vt4jjiXVsyCQU{BIC@Zg(8BwSX0oxPPKHgBkwp&qyNd>cx$6ZgQ*WbcoRDn&ch0P3zv#p5aCusCI0-&M+5rRF^rr-+@CsgVzOea z5gT@|q;RP)XR73b_CV?w=%t4wP*6{O(ng=|cDeYIi z|JFR0T&lqmooOgm3=9JWl(HVt`=LO+nh8=m<7)N11H@*3fJ%iqgiFn4= ztzT$;h77Eu&DJI!kMO-t=Cr#`teWLTuov(6k^U?%NX2musU#(p-E{sw}4tw2LM7b$4Gu z#?aM_Je3iqb=-x@zRQqmNUgR*r?cZK8?=Jjlqx0*-!o{wALkwToM5 zRWBEwA4PA&pQTP{QrhugPsXH*E_$NONdF-mD_ySq%*g~vp`wSCe?Hqu(==aim=%HE zfFD%}HBR?a*JG!@W*{Q!r;Gn*)aI5fTJ&f>`}S^+IuD~!fjl1@G+ICPQU09m*e0~ z=ICXH;6KDb@(Z=ctKPZrx$3@i#Rsw=BRQ3QoN_SiZ=B&kREqF>R^@9*uJB;-ctvA@ zzCG9Jrs*V2p1jNe00e|Vp6NvqEFu5a(6;c-Q$X7X_{+Zzq_6-?vR6Ho{CUWCgKZH(-Gh<+(kM8hKl+jl&^}&26{oG@qft~1 zOIA|fat3m2=;pH&N(_fc4k?harON|}_BlW6q5Hi2%m)U53KNyey`#4fKJ1dQ zC_hPemXKnrwx+nrIvKybud@+X48)6(W}wjR{|A5KkWG|`!+yq6L{&Dn&6_6%K_~b0 zg5{?Fw!r=oCLM_VU+5(1_+BEG2F*koF5o*3Z5RE=Tyfp(%gPM7EI}5+lD^qbMXRBw zM(*nslQaLf9?EIV#ln|oWM-0d9dGdc1IE@@iOR6{E!@&a+BQR3}OI2wt|(m`h4HmBtkl}hfeLF8W;i1cCr`c58|^W-9-NNb|C6$(Y#|t zQ2x4pmWQo=KrjJrXzk8}&A0DTxfJh#`W?GB4MH5^T1HxErQn7ilC~_Zfe2#?15JO1 z^BHDqYcVMVxa)*ci$ihls6c}dAr49Z9f< ze;o~${Se^_G-R;BGt^vS(OWrSJyxikspJAEN={@Pf#`sJtXbR}G?pt8{~H|R$$|-R z2VBL^OZoBdE)xc}4`Nf)yfR}qfFO9;=?byi(Ha2apV5y7!h=yf+RPlC`>9wxIQ`kF z)W{=7K*_+~%{(o^M8zD+gz|HnFeM>@fesAQwHpr=Sl;>X+kdil0qSGyU}hHPffY&@ z{{j8ui9OkOwcOD^@3wvFOzC#jZZ%_DM}_D**L!ZVpYkc2wwzosuK?^XDk4m2000ZP zNB{rh?SEOkSdAg^j%~E-T+V!DA0;mae~{JeX2U74ZJWiyTOs`TkLzvfAVN(b;JY>R|^aGCo00Yv7yB)09YD9ph7vy#;YbLNPw&g)lLq3}lBbj$}h$Lfi=V zTs1e!3ymB&-v@sC$9iPz+Wyu4ciQ{z+B3HGX_B%KVk)<7WL)J}aB&O!`bX&{(!U=? zFhoB5*}F^0Sc}`;+0roEg?N<>WI$nX{r0H?JvL5BRtJL*hsrfCJDqmP?e{mBWm@z? zx4mi*$N}QakvzQ>063#SA=F~hN0OtDvUuH#+js+_Vv%pyC=X>?uzl2f(#2SHZ=rHa zBg5zXokO-Ic``k%pGkn_ZQxI*EY-}nZn}ML5~VCSvAr90lQoko z|9-Z)$R<PZ-YMf>3>mqYA}P(m&ed3ED;rL$=oH%>L2V(`NnQ{ z?f>fRVx#bI;9iWfVjwnH?jW7QQy_C6H0J-y+FZmPgSr5lX9O^}*KcpdJb0djj7P7O zVhr$k_n7*{@bV*mb7436%rCG{AFluT;rhz}7NPRtMHC6L!8M!xA`mEKErlP2?zBX* z6WGd(i%DU+WV-CC4%(dh4frVR$CS&YFU*3LZM<5$^N&MnJH%!p5HrmdLZdI5(eXyJrWyr%p=s+lo5I!=vt~cIYetjgJ2wQH&W9t+G%pcfVAu`30$wTO)Q25 z4g$R#B5KC$IxeG^gNJ$E^+vT6huY8 z?Y-)Zpir$sLJ{g5`8mtVC4>Di(K)fzPa9tpEy?Dpi_rdAv)S2?gyR!VOYwAidX78C zF}hfi4!busQ$XulKbh`;xcnP|OYq`bdjjA900wPAn+{2+L2Q{!3jcP`u)8~cJ3guL z35`cxf`2XmdQ+J0J^^Q$JaYcaeZnFBb>iud4CsES7WRzKC8?3t!aWCUFAq&F&Y8+* zSK4)i7GV(B*8klxN%O%n9NnL_AU95y=1P1N{qDr`DEJs&bMnqF-eWb7^I<03VJx{Q zUa{bk#QWJn8Z#9q1Ft@(bn_W`9;b8$n<7uUaFyca&e-wuFx@SlMFrr zuZ3tYRN!7Nq^+58Y5aU5ud8pNRpJcF7@g8Zd<<^3K}2KQ*!4Lmo}c-Kv|`@%hTcbA z4-~YB2V6KjCh&B$c@^bPm}H5eBuk=mLC~htJ4GLp8eFXveMGK|;p?63=qUBkHm#W6 z{s2tj4kEBs25Mfl#u zhh>@=V4kcsh&h3EO6iZ5t^dPLYHPruisif1aB+8!D@6fY7c|$xK@U9i2r&XKT-ney zY>vCXddiDzvo?0#t!rsWN0PZ&m)PzMmx4*E2FO}-SjGr{J%q+XgC<1|3BPOGT29EX z*A=c2n?Yh5T_%{os_h%hx^3LMC9ZV&VQMMfSPCJxE=o>}w8&b$v99@s;dXp-~HDh5^j?e&yiV4S||@k(-w|I<1g31j+Sup(dY4ZnpqBEqJdi$;g+?$v~o z&TU8TLRet?J*OH3%~i!3$FRRBDqzR0hFpq#p-;Dl3*OBQOlmOG&@r;c*s`tqnz#3F-;r=l^LjyuNPISi7vb?I}iQIB^`KMzu3%)TBux(y6Gf#3PVE8U*~ZSd^4{R_D`$kQe_E)v z1n`|(bW{%>DJS}4KQ_kY^|mmCY>m2G*=N)pcS$7V(M&%kUZYv*w!eTb@fJeFE=YSy) zW)g;9zwy@FD+kuY$s$Sx+o^hYSX4F-9nSDwhNW4_=K?!2po-$n&Y(7T!4G6Azd7!^ zSTgFrd1^ZOgi-&qBLn1C&K#HMO-85wh)Z!`bS%_NRXA9X*l5 zc=6QuY^4(GRhga;^aIX={}$(ac$QI0YXqenGAf&s_6>bmXpSl3QM`F*g)|Y1wPwx= zVZ+T_`T>!9YV(P16T-MEgu3V}z}3!=&MHA0o%PiME~RNUYFY3cpDLR0U{wDCIowGl z)N%VWA88;ypTTg~nqGVllg5c7HPdKSbON=b4znVE`>ir*4d4;T9W?${hCjcdBQS`h{Uiug1uSnc6QgfZFwGYdiybF=LLVEIM>>Nn=Xt?O)x!tG=G z&qBCBhTVA%c}O~}D6q)PfCXv2{OD|utX3PS9)*4_t4I(`pIV<>nu3GCX|;7jkOAK+Tm&kVQAe zoG=#EtxfFRRQ~<%=;^dpHHd_vNGJFbw}y}{eI0E-<9-sd4{)xBqJmaS0K}dPb`8-5 zgXixRWH}pj554W)YaSRsD*q{!8hg!4%j%q$aibhh!NkHqka`*tqnzj2!Eu+F9#+%h z-CEj4H0S3bRPlTN000D|L7yx|6)YkD(K|1UJMN!${jy!E$M5`$+isfweRjd<7Ny(7 z&dp~wNEiS-?EwLs;RF~W4J=~d#F+f--)@dd6-miywv>`c=gsM+$*<2lE=P+I89>u7 zSHx)_)YlpR|K)P-P`%Ll{Ny%12c9}TWWnqFcm#O^0|fpYQnxBL|Nf|6{dJ4rttB@~ zqAUFvMJEA&x9kiuM$Sb&(f8Au3)xf8(&xj}8P+kssP;A;IiW{b1A0@0&x28Bg!^QZ z$dkV8l0o@LhSI0DTe^D*EX|^NN8iS$!4n4aHHu4v{gVc~RS16{gFWs~9msEBYO-g+ zt6Q*4S4{0jePh?d19{LSi_`xF9FYKGly0f? zUH@VEM_ru3CW1^B_mAUk9$~nx;g?6ODaWBT*OnJ37T1VU6M5-MNa0TGrFagV|yg68g}QX7>Q82Q&_DpRpfQ}#?_ zydg+#ki@)S$(bXeR$~<|QxndBwKy%WbidY6<`CzXksGvXpBaCMs-<6tuOm6K^0 zuW9~3$aS=^8KbfNm1I5n-0>!VL4{6^M3xT|gN5-dMLv#bhAd+tBUf@X-LuGc8rEO& zkdFWGGv1(;HGM9fsCoZ`cs}-mhgy+wx~4SvnD?D|+oUCVPOlRaRZ4d+0)KOx2ijC` zkiW7zRJOa8am~{nM&l~8ShhkW4uG$-aH?i^RJm`vQB`RuySc@ z^)ei!GjJh+za4T#)BNd~m!%jR1VDaZL4Sjjq@=#L7~Vty=-BB-qq#FMx5+du>}RbT{}nj8ZjRKwF`vK z@#~c{>@O7*1#5)d8Jmnk!T_k|)|^=$8L#prscDMiIEs80p+fAYUo#S{5H)eGrNzKh zhSiTa5K_{)`i<&GY0Trf3d&4IUEP%6qFDkl@S$WhaWzg{m*n-T?1_hu$f6e)B zuhs{3)$lsFLvUX;)Qif}eW?^oLE}%z31_=UG|d^)S0r&#rEpbnA2t1h0MDLCF=nJa zH_DCTdBR3{s;oK9YuT^`=;zlgYpnMCI0qe8jGeTi2wkH)69rFC&az6f_wj*N#m`j(%6TOtu{3P4y5E0xm$p!;pWJE2zMEn_LNl_l}EbVv@ z5&%wua94&Jf@G-?f>;m4Lha;guf2vS1LY{?7W5)kIU{jcsLvDq8 z5)w(*^TgQQwM%2x8HT?hd`}T1v>NtuZL0V9Hrc8nTXr@9LwVn@md5`0oDroFup9XuSu-y`pSvwH+pVv>Qp13*h0U#le&`p}|OAIo*q80eJ==6RcFcFmz; zIvT!)xo!>%;G$K&nR3BGEy+XGyt_Vb2(b6uZtQtz>{*J( zWk&K|+PmEaM~%ZuH>cCf+|VX*nm(KAUA@sNDC!@|D}5#{kP>8S5(-6zNr>R5?ulDl z7>G>{asB57i=E<~S46SwKh6Km7qgfv4t`ITu{K}PZtl^f2IW#lvu8RtOYJfjfb~M6 z6jR>Rud3#qYwL?2*AV)KpF|Hy@_J z9qI!}^9Up(6g&4k4RF>02|Xd$Xz#)J{!ddwcI&y_#XFmS(gyMj$sFnPFYupe5p*CCx8`O8n3hf~}&@mKrs^;DKAOLQ? zN1ik*l>THNlvKBB&g*&w`LiMcBzz-8B@%Nv4#Hh5RT7@DDv)azH_Fn0(%sDcZTtS;yvJx=D4mrVMNLQ{f%TzNiWkyu}?0 z>bJRCWrWLP8wtFTJ5s8#SH7NE%bnai{$E|-vIb*HnJmyVF%%guHXBxhAupMtL7x6h zBoc-;O5*ky=<07^<5+`q#pcq_)I1ZOTdH?c^kr9CmN8B9GB5 z2g_t!{RH(7Ktl)uW;+U+#GZ7Wx2X|*MBM<7j4O`R`MQt#Oc>h!AfDYn4WcJMrg4}5 zpaU{qXenwm4gY(=X7{2-c9iQuO})Wf>jho^#WNa-z)A7eAy}RM^gf6Q1yp;aU_*vt zH*0mG$L+9?M%AM^?aiHoJ-dA0@*?H>%T|06^)71wkUatf;-jKvx6P_+l^fFOu z7T#GywuFQuM+W)hM3%nzg%u8N0W?6d2>1)IUBHALQInx=<_3@q1r*FxDh0?Mm!8r;0xR><&iXA}Yhi=Y4g534xKKy;8Ts+k5=? zJ&wnTn;`8a=#cMj2VqZy!!@Z{jg_0Tjq_V8Xdn02_`t z&rehf48d`3P>ab;P}fyiQn8elu^)m+^G|9*u=HAS2qojDqe?bd!blWs^dK@R8Ov=Y zK~-@&lcLI}E2BVodkXP}NBA@(NOFO?JP{odV*XKQDV-XiniP}q}2ZC7~ca+7xSVe;cfmc)YX}=-_pV>eqo7b_rmv%`6@Y} z4!QMov_SEV>erTL+$vqNW^mfNS3@Ta{0fp+kuijaOcOH!xHo9gIQX{&uA?@7L%i@D zR!=ukZ}gqNvKPsHFSSfm|20*toJpMr2$hPJ6u7{Ar;UGM;^(6Yj&A+WpNMFm zUbbvSqEfZ4chy*8n?_fIEv2jdfke0Ze;$pVSm-OAU3mLf*<~0{=+I?Cb0xAlLKvDy z>y4_@Jpc2|cjz4z!;OIfFrVP=c)hGpa}OFUUPz5SOy5}Mn7d^)!MV)oUMtt(j4fSr z1ysH0ka9hKFbg9*xb55y39GcSynhc;(UcVKCex_M0Wx-VpV=LL!xpq)#MR>a1))^H z9=o7P)Spy0cl>IYoKEZU`7XHtw)#|hPs6J>bA6zksxgCWRY?*3=Z#RU%Cg4KY|8JGI_q1i(; zhly>Gt)lR1bH#zjJtA3gv?-&0`T4vgiPn)sFJ^nOCO9TDyS)H;uluyo7;B3kYv-z5 z*F#o0KS{*5fx2;ICOf4WcA+)dxEP*No4`Qxmm|q?^#fcV$Yu1o=WQ?mjCr4qoapLm z6B$0ZGMv!awTbLJg-U_!pP&{}8QgmF)N9L8>R0%N(X;WY7w@^TvpP8ohv>;c|kECjr2A*6{bh`yg-Apigbs8T=-dW!nppPp(F)Zd6NPpG_qndxl83Mv+LNi(! zSHM=*f@Dxf8882jFNRj)gV4slRzB{5PRSd?*@fGImruDYJ|f7z=vltyLFy9CVP+#e z_qqd5`8yu>TpoS`7RtI(UDe7tKMlUwZ=X$w*Ok{n;hJRFLzJ0}7!KbFPH#^)JPDvo z|8Yx?W${It`C540$uv}pK@v6o?A+r59(xC zCc5CZpPY|436wF5$)w*sM!?7R_FAp9L3ufAE_oN^omI%3(W%3r*r^<#OA-aoqc@k_ zrMsd{KtItQinbl~y~ISR`QPl|xu?k!8Hc|5i0$7>&ml_vG>3V9}X&A%@;da*=r6s57Pr9QvinHz%3TY$9|&ISY;dQ6y&3e0fqYVZi4ocLdFpXRRjH!G zqo|ThGYz7W1z;mN3+`G~I0JUeT`Sa|==@|PlU3IPC5#dSzHiCFJz)uJO|i^MG*XDL z-4Esk1L=y+U{OOi!ti70IH7b%UW+`Z40e)eaYJk1gMW{??eyHkyZf|{&$3ArS$KzF zXDcn&9Da(`n43RD+>UJzs>O-8%jliO<7a(Va7mpnT+pZqZdQgAI!0Ne}Pl?Tew^YtzE3wI5Lo zmx2PBW&t>_0^=RrO>FE;k%2+R%6eeQ0+<~U^ou%}VRy+(MW_QDL^0U2LcRoa4ffy8 zci$Jyc|5dn(zSSEOAL<}Xq0~UcyQ-Ed{T9kn8^DMXR9D=uX@{PX6U#R_2bcZMLoa? zYmj&%{!f1&jmorUjTcrJng-B33nI+G0D@1(luv7Q)ze;xV%pvA9LBV-+7!}MswOM< za5#aT;QR8ja0-sxkbu8($vE^Vk{#GoWV!_XetIw}`3_OT#2?HyzWS7;nGc_wq1#PY zZGG(B`oCiV^)l9HKWoBbYw$Wcx?7I4Wt2We-dGWVox?nac6|{|eWLGZ@0qV+$Idx^ zuwPSUYu;`L3sjmoTW}*7;#FE%-d$H);7OtuX%y-^_xbimIU62S!Qj9>vXDVLFOlt=AWPUwn(Jn#CNyg{ca9h2gCJ zGVex;45)95FL5mbIo}lMMEr7tl>}wEM+PUTM`W5Plk{Zh{NuT<^+qNQxazYdGTs_MD zQOydjer(6Z6u-Cj@q3Yb~z9d@OWYrRM2z7QusRVyqBe$IS1-JK8d-`?m{O82O zf0x<>1t@?2?)=+WyK(q@^nZyw%c(A^A4l9}PI(}Xu$2Va$Gh^EN8fjNsT}O+`kv8z z9bt8cak*(tuMV675>?P4B4YYa_m6A=OVj4fChVBpa^Vx!_k1NEQEmln3;TT!9 za(aNH$Vie}^A1>JGD6*x=(5UN(u|LSr$`Yjt{Vta`d%j{fxa8@K1uw4iHQil`xNd= zg5t+Sn<7ZLVNv|*6LImHAqpZq^>V%&UHy7ErJt{NymwRWyV27HKkMl>9kiOB?r~O} zZXNGE?OUy#a39<2rw6J1dIYA)i#2Jcx(?Q)kT zT#D9aF)33G>9$SmQ{7O5bl~-3W$Rmyk)&)ruYSf+#*FMvVDUJV-}e_(51L8|>OW-^ zzz&i{0_c`V=*=Em@1(c9z0hgEQ{=fn0fWn zCdj{a2>SCKA|XiLf{PGSfYAJhr|m@N8QSi&ESn>99>LO`t4pH9c6FkdS|aifi|qSo zSNl@`JjAh!o%QY!B`(=>byTU^IpT39eTexS84ydQG_Wm8xvY9?DKg1_iXj4e^p<(3 z+>EJI%TNxe!C3|_6U)#_z8ta&z#wt7U#=QAXql#844f%21X=}epTD7@0h(|rEDpnh zheN$;SVB`4wkIrjQ})6ATUY}vzk*T~I}Rnu+J@rM+cDOarcxzogY9YO;M`CI&`MoJ z(hBH>e0v`uD1~X!O_g(H2{;*K{oKa|sZ8$>X}MZ|P1Zu{9Bziq%bH^XY;B+G+1e`y ztj6%4O^78NOydw!64$U-68Dvk_q^RjVld)b%ZSj{8toFN3}u2K@jk&JvG> z@uHj=qq{e9JDRu*4M~(LSEB5dw?5^gz;}2SN*7nsy+_V!F2=A#~C!A zhT$i%<)7Ugl6Y&u5kaXoxRa$-l-)^~tnNwS+$4s{H)u@4+ca^0(B6qW-{0j27w-n@ zy&0$A;n0p{tk#bwYP|p8WN`sx$biD(<_OG z8%7tkD+O86H_XusLL*x7m>l(vk7HO%NjltunU5?8Q9lNKfa_vp3uF=T;n&svUsn!y zHyp_%>cEt2`(Sr#d?TGrQ~7bKLiZctIE30W2%F^Vrx3YB z1ZwbUjL=6U%c6>5eA1`6pztI{=L{aHC}usrMS1nJ{h#=HO9Tf60yF_= zWUI&(xDR(8w>%KI3kWCJ78>KfkN7Y-P(M%`rFKs3>o=0L1wNsIfQxB-vo%-6&DRxriU zBnhnHlJI*hLrvW~hx^~_J4UVSD448^!^Xcz+T3L-iZG5IO3~0|w(b7$Zlv|~ZY>8jD zhNj+B-@Wc9pkz}VK<~kYl}ZL~E_klZvlH%juWmZpoHys^a*?pxZEH6?K0iNHUf*By zxkwZG)v6l+00>e+o0dtaL2Q{!3BQFCw;5zOex%ha4xXMzjqLGI+IX$jOYZ1M=>T4I zVEP$`7j2n*zYkjf&Vh`-L5hDehtug9V%<**ruiOgm_B}1@58*Rj5Ph*@}>P(p}rw8 zALFlwpzCq?{sSiwvXDnhlSD{5|DeB;N!#Zk49p3O=`i$@y~nrQGX(E}|MBYwcf42G2F zh@TVA>N321iRCJ$YV%{5Cq1y^QXtp7O$yHIJnu!1=ilOtXL)zQH?+!D6~W*BkH;Uw zAtSiD!zE?w!P?J;vQtTOZCQT*v+-e^q;U$&|3~xopUfnK?KEs2tkn6f=Ixm~je_!( z+JvZmv{Xr|^OzCLsX_G5p|l{%#Y;NmRRm@aR^(JtCkQIeB{hr*&<31URJPPGR?VZk<(ui>Zz zi}+Wkn3Ua$5<{R@(a3tP7BTeg==JxCI^jDqDuoa%c476=9ovV@f;`K!$9E(iE#Wik zIR$zlZmR|-)pe-I@>-*fUDcNkpE1veIC~ZFns%?hZPIN+7Hg|_t|h(r8O9(0)Bx^f73uU*!m)4Rmva;y+x& z>HF`{g$=ZGrP1#Ngm*V%u9h>xlv^}{4V*W?niRNe-g)V1Y<`IJ*`RT%+Dxy#q`Gbv z+WRGRgIku@p9cFuapA}Gs%LE)@2GbGqW}0h?w2hDL3hBKD`0p}9tJ6?&N8OQhOGiu z!Y4YI(ll>dxx2mhaiN}f68Qv8%>s-eWF*lpKVi7hwbQ64AxZ$L*$){dEDoP=N*!O* z?Am((`+W~wy(Ii*>gVd^0yrSOK=iEpi}=?YCE$w@*I#bi_*|Y!8***i>!s`aDjXM% zjd0jUz@CZs+mXhnL4;+tK*^O(0~WqF^aqkOG2y`SsL?l;VIok8o>z0ryN2}Gk&+SK zMN-d7^&DRtCEYtuS2*2G7Q}4*;CtFKol5~{wM5vHJ^>!}?>4K{3bP{DLDP-qivo)$ zvysNDjZIg|Eh*|~{0e{1e?Z4cfrp$U+e6#rr@Y6K>7!mNv2z{evoUe z*}=3LYBHpQc@R^)`oM~V!T?H6FCRc7L>QB5UVbCRjjL>gj@g|lsYW+*C*>(e}^A)2Y!$9Zc+n1 zyVnM=cJz^5^6lE&BvOM~iC(q%dJy}*BtK2W5@%vd;N_cR=Z!cWrl4uK6;#(@w(-NJ zNe6ArI0a5cM>z_m0OQPhhAUfD+$n#3EkVM+5pJR)tl7!*wk@cGA3w<_+|jWKP2&vw zBfm*t)80z)^6gU_UFsYM;^mQhcI?0a(OGBbsxc4-#NdiApq+e49O9?`$5Kckl2H1E zD`off*^nF|sMe9h>q2{oS{W&xE)sByjtr`}y%!saq%r2c@l8|m)VM0QNf5J$MNAEn zWAp4_JaKEXYfl{WF8cP8K-(W<=*(J|0?QL$f!UFV_JoLIaVACZYb?qFHq#&?kubf==rHdS3>8sOLeWTfL;-ovx8?{`D4c ze8yP_Jz9O!oE6n8inrxYNkK%L-*0BjwedrLfD&>LGk70gL9Vn}xY%F9b>qF*Nhz`R zdd8sXxW$6n3s7C(hsFr>qOef))4ElpfT&+pQAL#XJ?=GF)=Q2jTZex17Qy2Wm%*re}7rlS*L z(}j6@zrAEz_$OMpa^WNV+3b|nfvcu9*L}OPS+YeC^yLO^2{JE7`#N)S>)n?G^&2=} z6W5uK5(X5S`+`A>?VRk?*w1x!ks;&G@NErf##iKR;5bKkZ<|d|!K$NwQA<8pd$}@i z35qx7>o=NQenqvJ@#Ig@=}oKMVW$wSKgYnN@cV*V6Z(>1(4PO+e+E^BX*)}C)BxNP7QAT?+i^JcwO-5z!R8^-a|lrSazw1LjjSp?DX|)l zquFntk6GRnio)34n64`@cjff5%U@xKgMGfJqQ3N=a7HUc%Box#m=z_az44F|q)FLD z_4P;@2n^Z6>Z;R^=VVOSJRY5(;o~gmqc8fmMEbZB?xq@|4rEX6+O^_i!5Vxq2^uRN zhp${{!Ebt;|`oL@k~ zhR|!ojTw0nPqE9FmMx{1CWv3CgS+X+QKPVz!D;7~;UvN)7cx@s{ny0&w;z?6Qu4}M z3lg^M82th54KCP04jxih+h?T$ANb=GYyU}GN?Gz_IlI=*z;zHY`pCcLuZ40GeYJy` zZ2XW9;2U3;YrDG7qHHxJ@&R)-oNM!phi&k;od5q8fF%BIDEq6mUd^>L5#E@TLr+!q zZ3@I)Mk1R#Tfbn4!THL{b3viRAwe*br1ci0UZ(}aFl4$MDagy&^7prC9B`bArORtL zT}gPfcg)TA;2Pse`uw!_qcAd|C29ps%^?|5K6i@sqSpXLFCv3FMpK3DnW>YDibBP&TSzXGBmn+j&Q^geG4PLo z{2T_6$z<|bm}-|QcoK^QCtjKS696jP9uSR35;0-^?0Y?+oqftCL3TMMDqDFjDyxbFfLUwX&FoUB>n!OSVqgL=gkFB5UQm(#a@L&Tv1rRtj zxOz^6`)UjBmPSzv1sFP7acpfJE%R_4++1GL8%Mv;mEy`^rT=4aIr*_#QxXRyOrvXp zV|p!2PXbD%X$CX3@Sh;e^R*gJoo}Q36!uPxx%5)&CAKMBsVAf0n!aMd9YlRX=F+ru zvMH5%{Xj@iq@~nFZ@*+oi1B2jN(YNC55AYN2SAR?iyi;;Yn z&yy1Uhhy`mt{d4O5^vX=&%yxP#NU$Zpgm`mdOG&^Nt?A{oO`V};a!6|!b(EFp@5N` zMunu`Et;)AM(EFY8nBU9gR=6HD)?4A-X>SmfjW4faEtKj0*9u4qm+HP|2Zj1*M||t z(!`;axb{|m$Olr3xJYRUa($)ukS>T$7)3LOLg;^D{eF5d%(^ATkUIV1hp%c6*1951 z=CLmOa!R%-0DlkL(rDLaN!3apP@2wwmxNklKoFfT9GL^|qUNI- zqj|E%AF}20XaCY8Y8zQQd;V)ENF8@EMxR7 z~>h{3yevTlflkh6p|m0MJSnwA>~{~IE;j=bl4i;gL#&%6SIGQrW4*I?(+H1eBI!PSU9*rnar0PSNw9=Uv$N( zEqV$-RMA`G7{kQ7Ik{0s4%W9=fPlOVoSqv|t60BUYA|vThS@}6gyweWqg`FY$XpJ) z%_J3$ZdsIvh|UT{0P#7pY=6l&O!_C6c1j$DvywRp#c~VFTn5Eb)418|sU?)?$A&w! zKj0l|>wJgpf)Q6>N~WKAxFvtw%K(MvmXc_0vqpB_oG$>y-e@+xqJv_VrL>9Hp9hM> zDa{bDUz>o7Flk6arYP5Qe8rby|Mp;kLQz;@TT)bD6)Oq|A3&fv<$7&H z8UD`qy_{oDtsex;7|)wMOX?weJ7OC_8RX34Tb4o_S(!U@*1%zy)SgB2J@3j-V@R!* zs^s7PrOrR1?UUa9bfu&YRR3tS)Cm1gfwhey@kj!Q6D+Ue#cx9;0J)hLir0%zPabXS z0INSdC>WO?g~pS0ANF7i{2QWf0X=;+_hfka2J$W9(P!ra5<7xF1~F1Lhc zQyz%tx<+2f{O)HQjjH*0`d#c77DT_YY{ftP&7Q=d_Hc^SPtY)y#}*Np{pHZR+}2qz zZ9H<#KE&5jI*I*SMykHmw5^Etc_q$Z={2d6v;<2vcEVQ^xx9k(w=ZZ!4|tj+)?vjy^M>cdheIe@5qb06uFqqSdG(&Uwm*U< zdatEOBT0^=5SyqMXtcd0rFadgn$8M}LvZ>Nu9WsYZ}QeFhShvTPIu#oxY_qOIB&o7 zu7(bR$r3@}9VT4#Z|}q;KWc>$A&6s=K2KM86HpT6D7M_^yQXtN^6RR_|MtqLw=7z# z?H2+Os9*0WwoSoIJ?11aEBdxz3}0EoGOWZ2V8E*(W!+)ZBM#gWP_gV~|K&;$?RfB5 zjsk#AOxnpiH{OTBTASf1ZOQI*6s1~Y^2^|z{K6-} zYWkm*Qx&kEjU?HJv6{=F)0#0<`JYuYPhaPa;W7uHxWz0FQ~InjYzzD#GH=ezVFTZ~ zreYn4o|!NR#(An4KdMC#4mIADfJNj3XKHUD%CkFxiaMgT|6op~2I3W%kNUBs0z(+` z;L<`+__%4JqZS&kfL8?WxBRz9EM5Rr8h6H}2U+Rcq0{0gGlJ zo=URgvD*)7$d>{&kmEhwHuGQLm%g8OrjYglFly1EAwzjn3^W^$zBIG{@X=G;RMXh) z%&1!5k7(#*Nx&bg{@etcpj+nm*n6G-nDE`j08L;By1BiiN}VVh-alidxrRop#FfWI zP_I=t-FvmDi}Xfl87?PeP`7>q@8!gAk&c})000CG0iV@sLSNF2YBmeoR~%tpf%7k^ z!0xjW5P1w&dIYF)S3^`0JrKrC_^urmT(5$Qju(7t2HuFsdiLOLMu73v0gx+i)ZZ8X z`rKJ^5QO+(9WWyIlwAGd%}4(gb>rJa&-9c^=63udi5^Qxei(R^l3GY8N0ROt5KiO+ zO6waQ8&#{V9bhgX{WF&Bvo%~(Q$*|sp*{z_`z-Q$SO2&@_FGELZ8II_IiDp6Tht2d z_@KTR*P+Sl{f_HZ!YF6Tg;;oUxUAB%;{AMHtYTT z*k9Xams(5x-(B0)R=(Kjkm`;Cfy>qTlLU={*hU=HCI@)q*m5dyrwjy=l!o9t z(eVj{r=4MLaEod6kB)2GD;c35|KGAtF&hkO%Nt*ciaV2poO!AGz1k}o)!8)mBrQA= z(!jb8s=%^cgGC7ws*M-q+`|8!Rp5AhY!n}Z^N-2Q9xny-W@RYgs3M=FSgwoudO|X} z(GVxv%KtNclYu7+fPifzn97mx>glL#Gl_0#u3ZNbjM)6Ay9ovQM+ko}>WKLkTXM_- zynP}t>YkVxPFy*_ywbk2Kg&lAwT(lL!wqtxkc(}#0mCG|sH(B%lqB_-e(iTX(Bxwq z9M;4aSUAV;+n#~!Pm)xDmD#JoX%PVIGb;7<;gZ6g`4Q|!)K&CcJSs`LQ)HSbwgt1S z9NZ+ycEq-i)tq4eS!quhrU}qH#7g)SY}$C12ya@xHQ*;jG(qH^lf4U_UOH25!`kM$neth6c<|GAoF9CH zMg$K=q1*!erCO$5P@pAQ0%E${V`UA?%z+qHF=lwOfB*mo;X#|)NvJ_=nM??;l2ez6 zjmdMtsL&WAcKrUxxKW;oHyZlvU;qzX`66?@TR!n;AU{i8$^JDuh7WWty_0r z1WWY!l;>kpXrpsP{Z|gA7+XBIe?$-CPBu1*- z!=`I=UcUgZ@!*2^uP3LSnYbov!T|LZQ^i?(PA{*2HVNLCHe73Fx0+uEPfv)D20#hC zO#aP@3h>|iV^Hq-5EH*jO*$4~mkyTf3RB233|ck}?(_8@GiB-O7}T(v&w5%32$0QN z!zWn#%jquaGK-HR!-XP0iuhs=EK_ivawE#MU;z49B$x;HtZrMD?e7`$2Uf<{lfUU( zr%kqojsA_Zhtt`!J7eh_Rhjax^mt%1BYS9=*wa<;k@fa_n`DYcqzN^>FKis(DiJ>W zRdO!J_S6VcjW`(3mOp<=Ykk7wb8Y#tD%}D)9RcP!u7pGV+$leWn_^sQucNEq`f(^x zl#?YC**k>((1jiTg#PXO^JWp%Ku5#}grJJZX}jm5~v=s?#=#nS0V_ogi88IK#7K^bM7K^V;1dfx`Uj`W7nrcrdKsH z+L4>!;=^;_+QRU9R1zUECE6!CNea--bfb9MYxOLdpvBHD-hXq zeZm0DW)@sVti;p<3CPJrar27pt9C4+;-N9}=6BW%_k@gre1!*Go0=<=SOtj$FYfu) zbX9KvY9=C>tZZ5Xi1$YDhe(*0+BNAXF6trALrypRAeooAQxL?H32XY7cM72@avm~* zb$!0Gvd4f<{&2A{)G+0JlDL(xhvK##bUqZ5ne}cdPJJNSYybXGXuxGxYbjOVMq%DV zbwJN#6&&1pgSMgF*LrEhJEq9RbjZX;D(@(JkWYtyvz1)5Or=z<9+`LC?a?Iw*t^XME)5LY|9_*b$5b$ z-r(57uf_6C4EQQN%-J_Idv?wCzCdW7l2l~aG`r;= zQ$)h>fn?y=j$QU7H!Za$$SCQ)^$NnFMGGv1AXZ?<*33sdM+`0q$*mnHj7s6^^=>~YQ1RcOukl&yXYxM{sIx}u`tBBXA^OHz4Ka)Yg->$& z7?B&C9_H2*k)YBf-SE^sp6*-c)z=h~MVsZc43;HRz-(jR+i)U%jHL+q#i!?+`pQQ~F!enpLlgdG z{=JZ{ke|NsX_5_8t@3Bgk{QBIJF0r$@ptLZYNu^+CE$nWGAUV~pp9s8_S>9w=+$D7 z&Uo5(3GTyG?LVaILQ0Sz!jt%`(j07oZnLJxgl9m>y>gMR`g#$eG5lu$nmg{S=c^Q+tRKBdSv<`BK zyEJUoyAS6D{G7i@v`*+Mf}5rpbsf-NWHvv8Dx{$`g2z3Psx@P2M}g_v?{@10O@DJhV6ms~2`kNipjv zo!);|!HrlSRP?=wbPWY*1$0WiFn0HM zPEt)mXt^sm>`W!0?Up2|18JFTK!Ma6PF7A6ybovTD z5+d$t+DjNtpBFn=pn;PXL2)jZdZUe5)&vd9{aiao$YoQe!9V~qfS+?v6Wxsu8;#i9(jD|7>q*lt!E%kkD;f;9* zw0OZkJ+ue&0bri!D<-6aO(l*4wNYWVCk7Oxy-$A(CeQd4OpY4=^+_e{paFkVlfX~_ zRJ;L{=Z6lef=5r5L}2W)ljCX9w{8_YiPQbw5spIkm485l)Ce4DkQZdAym6wDq%@~= zlNEyd_h{j6#oIoYy3$aaV`1V(N)s-GK$ig=IIJGPXCfmXs;EQFAU*&HsDF|(EN(^K z5|FrPbZsA}CPo1l!_wFOw>;2jENWRV5fK0Y24F#-_(c^gchxglGXIXUelsK@?qexC z58P+cxHeRpm$Df48%XbAk$o0FuAqwQ9Rz^tQ-x~14KVmP)*{r{i+ z9ON${A*_!vH>PV^7YLiuJMv;GtrH3^560$%2IFspQ*Su@f~tU|gZun^tww~Xi`_?@ zA!La^zuy{bdJ^+%oCtd?7I?}ReYUB-R+Y?ioUn_Qro7sW_*<#27Esy!lQ^&b=~@q_ zFHiK-l#)d%#Vruxw&D~i!wX{PL3_Z>^IV3M_*$9!5g`k-MDF*GP5{}gMuBWowS(4i;(iYG* zU-E_&=XLm@!Exgxxg|xPA13z!sYuy0`wO^;TDn1kLf`IBDa#EK;z_WkpbaOZ;L^Ln z;EH%2JJAEi6|mtZj}1CzE;^)IZ#v0x3}H5xe;(Kq@C14xX0oUMi~8*QPAAK1|pSK!fhTTs3D zan&G(RMQYd5#-hwT}r7pvqow+z~1A&nG>&y%2!^Pgpd_f+x}Gt03{WKjQ?>5?V)KT zFBs-^lO_Gos)%IC+l*`OP8E3ht`ypP83cn?!IXR6m~ zfe4^Hbfr4oQlp!G^dkHK-P|wAp5Goj7Cz=v}#^n|$P zB&tO}w|FZg1g^zo_)+~Y+sR5%7@~MJHPa})0qN3W(6yVYMTivNcVHImrNuP_yo~Z7{g>Mg6Ast*AeejLmr+T zu>Ef#^$|b{+vp^k1@gx7*^RjpPCvztadk{%^`qE!0NOxFkOv^Ei5eJYo!vsf2feF4 z+GtADzOERW6hA7PSL8BbcEwRily7W(oLwQ>7n8;gZ-2`%Y!wV6@LQK!Tw7J}bq$=O zoTA<7PA|~}VNOe=Vtcz75f@=c-;~RUqh0FT6n1M^y?a=5YY6-2 zMHJ8@fBy7EIQbnwd6^f8eZ}V+9d8tD0Z$i?b&s!)C1DAM-afkvNcv6)}gx5 zvIhI!k)a?j0Q!|e>QJRP4gjgs=~WjFUYykR{)PMf3CB3vNU}_Du+tSf=@9rt}@~uSVu`WgDj`u$*<9P ziJGmk#!0czd~w>O@PcKPSt!xJ`~lviuba>Y(?0hPbRL?+c}REG$t^`l?vpi{?0V`b zL%2k$7O|1lGE1NXWl`eFcP39AS#>Wv^OyGu(WdN+HtaxycNf=sqmLbVzJaSuWk?SM zw#@GT2i1Pk%wgSkI4ydE-!qiBp`a#z|I}{cNbI5wShlV&$rluJy;Y><7dvEoij1t%8Xa>jsn(wiOKuD6!2L zk}x$mb^{z9mAyenj32b(?(@rsUv$ZW)t-TuUbc(bW+M7P)@j=5d}P+U|Nm@z(ur$T ze@O2lf7Z!*Oy1&FMpr^UN%Rwc$Akp<7xyXIP0{Rnt!cg&hzDrA5(Sb-UhSH*c5DvD zd5|$Xi2`zoFcb5z`Ho;mkI|J$tIKbI8%Qw2eu)?M)kZ#`ox6>_`p(D0vtt;y;CXki zrm=4=Y~xAi|4bM9Tri=W^nXVf|FZwq6@Gc68~3{{`Qq4IyDd=kQKUycOoSob&^cpG zH#l?9cq|_mST^0syTZ;&{~|xhAK}XB)_WRz2X?c7AIz``BeMihPFkr>L^Et6wC$N} z<=1~?&rrg4BGJFV){Kq{zm?)%qgK3alsaGAJ*?s#qaIvkEmlhM=|ExhO^ISOeEP*T z9Y3sX7YEavs`UBQbNAS03@kqYX1kDccOk{&MI~JDWJczx@DG(0S_K~p-KcC!u{v%J z$wC!T=8`P*e(;G!z+$?Wfe?P5O6oc7G`e!uzFy%1Rx&#lEi8}ydj_;8g%2Jbz2Q*kB~X}6dCe10^I4^ii^~ve zsi8D{dfb`hDy)zyGFx9K?bmLKx0Qu~)EJ-S{8ma6U{#RvW^voWt3o0)BEJQgVgYvl z+830*AYqe!r9!tub>dW4?xvrvIxzk1j)n{-Y*jGM-MS@05na)PnS29&7b3M>a-maa zuSPLot0t6doLUS~5lCnfR?YQ*mTC#hMj)Xog5WV-$E-{N85oUB1Nx3~oN{KMQYKFG zUtU>S6r3@8L7gl6RsEntJHVIEX1MVil}Uir%1eE&5I~S9PBb)-ATzsikV8%Uhlf=* zS>>I81NIKPy@bNdKhi>4a1K3jFR3fS!F9BrqgL52fg5@iU5vXa7sViN4R{9l48Q-# zwCgWtPKrH9{g~kwP>A2o59&+tvNr$v@&xMPI`7Gt(s?DO-EN3tnDaFtymd5FcsLef zlx_wXMdVx-FCEpvm+Lt?~|!5_@QUbXs!iK00#_9|~&Z^Fq=AlifI?%$4Z<5f)@~ z$z2^{Ch>$-1Quls3W&F z@zWj<000EZ0iGFZLSNO)Z1t$#J*SJ;jgz`>7Rkijm|zvVfvZ z=#!aoY2QyFMRs9NU^oa}do_Hb9VpS?dc$s{Kgw5zinZlbsO0y zEM#dUn=}b%X}?-O2k}pY)IK`wccwU5!Ay`S%X-PIxTT2v*wF-JmMEFy=S_k39#{Xy zl?5QGHF?3C`qo~A>y$#iPW zjwRSfIHF>|f=x(LkZg}y$UnN1f1B*9lS2MJ9%@EoFCTMQLl-BlH^s*k+xm~#hPY-Z zP{D37y@~5n)Yxh%`u;ekgoW_fB15?|y*;vyZDi_TxzIDf zci-QK_=TkbRoE;KE_}T29RH%0xFHfiLxt9 z>vBbfbT=Sh{w#>Jb1KNyZ22v9F?$lAx+Eor1Y5NCuVZ$8o~9o{8g8*uJLElXaWZa| z^c|f;3w;77^V7~6HrwGeI1u&~a}_VCAGSNO)_e+s4)Fs29Axk(h+{iMeQT16+?I}# zFM_wZssKKs%NJk@j#1Z`>%soM)CADK3eWqu!!}ZeH&Q!;OaWqkfK)lUu{N7 zjEOE>K4;{-QkiXJoIk+nn)`baeaHXSEils$%o0i7>LUc89I&NUMJhwDd~+34g!s`I zHm#itf2tC;BX2q@G=xnHu;IgHihJAF<{DbLwlDeN#JI% z0fGTw1606KyhBL1?!8#2$g}*~M%a5>e7wG8{MQP@+h@Y(Xe`w{eJNh;To#_$uS@$U?wihgo{{&{xJOR`du(nHS?qH(y%J zHMV?_j>STbnPi8V!kHuXU&K(p4~|Nn=aT0L=U6l|h#}>WFmnI;emntwJSVe?r?`!4 zU!PtIWWg@t&GyOv>KkI)?AnJA$K#~{ta&{feM}fIK&d|iK>z>-=s}ttNvJ_=nM?>v z|Er|C;)6KsJ9c+dHSk7GlegvbPWe7fAf;f`ZJgF z=yK`9V4Mh}Z`o_J3jL-ApssLm)f}X=1jmy@y#ZGS`-!?I>X~HTHMQM|DboDm%HHUd zE~!?Zll^iIe!pb*lkE<$X@UVt_*-)@flp1^j-vAj1bPG!87Kqo^5G%h+0K-wX|Q_$ z=C>FsNcvwmTQ2=(_xfOG^^JNvqo!c$be|QT8zoJ!AapTVb+FST!P(Kzu@jpk2wGXH z!JpzClIfBlGq8mGe!+z(`AU>Gg2mq~+kWPOVcI)j*KMEC`!O=r>wk@IWp;EEpDc!K zz?}LYLP(<5Bh^)bX@iH7Am-vERm@lf8Ge)gbb(2IWeZ)MUjyfk?9tku6<3xAqB_6X zseQ)w!-W7DLfZfz2H|9FAule=Fze`vK1#r6TB1$%kGtyLDwy;7#Q8m#06@9D0$fyT z(=f-~CX0ut6i33OL#KN+&p)QCmVA~vWQKfDR~1K}j{fpM&lU8n`}cnJ&s?kFlH**s z4NwY&E-b{}UZ!@_tq5P#G_k4t7n!GXog$W)L^>$1yVI@`er#}_Cdj5SqjOT0hh9`=6i(V>V5}}!Vg#JHJ&raWumhqnpB9uspOhcSXtRyU(k_cu!{r2 z{Li1jsqww0b+R4OQm7ixpST`xZw~y;59E)I8&w&E)HoXUaCRlgf-{%5rfA96zAq;H zdfB*~z4+E^Ip5nxPtnidq@{MtF~GgLJO9utzGkGIR-eMpS79Rccm9QEqL&iw_@j-O zNcs+#4g1BqKTr{AfQh$&9)4V2^n`M$HRXBV28)ie?ECWPkJBV}Vm^p^340W2KXX+~ zMdE08)TjUKaZRyzbyesYB?K%p5}d(_`x(ZHMpxB2I}u{E2)kR89pC=$+qjQJiV498 zh4}V9;P#XVMbTYsnCEGu`YjRcOEZm{^U~9B3+}0yJ`!1VV0Gb7*ABiTQP2xQNY9qK zJ+n9_UpAfrjhaD-B?uplG6%jIVh0h*$A?$(c6?>Vo7iv39B!&m(mZtmR(%0EysgM& z;>rM;={@^tlwQz*1BdDPCPJvwqsh)kgwIj=uZES(u|wzMNUuD^vH<0O<1+yot|t5c z%kJrX7=1RzxLpit<*M`2I(qh(<{U80A#g!t~|7bRg_A=elw4$p#EBhjv| z`{jjt?!C9c?C0gxsxK20xo&!i)>RxC`pc9wj$dCa!!q~fFI`SVVyVAllNarF|7dEAIKL!<5Skv@dciWz z6q6o#*&aKoWDxD>^160^o=eBc)Wq{u@!)^OrdJBxYZsCaNI&CH)22v;O03A3653%& z(r3l!RTW&eKT6xK{52{N5D*;jv5rhryC5J9T4K$0=HL)T(!DArdQgdN2d+u(F@oBu z?91;E64C$Sx7~I~1#`_HE-oEjFr2f7TY-YrIc%_jMV1DykGM-PZmFBK7=$7sf z(8R)~BL@5vqmUBSh3uAhDeP)RtS;h4N@TcB)5og+uP^n+UY#JYQB3~e0G>0J`)5Rj zVREvADQ&SHkec6yz_Dv_azo_sEzw;S*vk+I6F4g`b7tB^uLBT*^EH_Uu_#hrn_~p{+1x!=lI0#Xd{MwH=KrFov|1EP< zJ^?1zA%?uQ5X3OoCJp3{1dh3Ep}e+>{MmazB*s-h4|U zt&|2T6O=1s_PS>0^6QR%zg|hmG8%{J_yvD9bdfCC0lF&{?wH_;o7EkiFxcW1&dKw?s17_@R zwwr7amW`xOjJ32x98LfdkG+N#R1*m{h=9^XEzI!jF@OKq#GU;w#jjv@r*-9Dfji80 z%%e#>a`QNtB!b5w!11R8_h%^m@m`(RFt!Z|<9bKX5M1;>-G z`zAm?$HZD4X}2B=(TP3=^fEF5^Pv;|axNc4xncq;RcERqb~4-= zTK%GWAO$TBVB-zEAm4ms3nZkn*>c$h6_5czGwWnIK|&F~Pp>e~!lfHyY%;hiKT?@| z$KH*Wo|1Jpr_Sx-&JVs9=`u*_~!w~4B@;0STP&;bY?PXvDWM*s3MhsI!(=|*FRXi}sNYAbtw91az{ zDaRCc4QgqSfErym3hQU25d;VLgbN%HbDm268Wrw0n%UE25!Illg~?y$0r^$#Bt2CxPT z&k$Tam}e%`3|uI$Z7;R{Y;{^-%<^Cq)o==FgrB>dbxx*Bipjr{00Mi6({oF@NvAxt z>;Ltfpq{(^sE;eH0@^>6vT}v;r|kzVl5`}ut5vIcwao6vIMmUXPpuT`>7; zLv*c@{z%U|ALY4GB>6m*`@HK3qGzm!B9C;9aLQ%zie8^$h=$~ulGS(<|KPQ$ zXuDEDUj!mgrW#BE#1K7&5rSsXHfFsHSd^$7sx=jGN2`h**Kr=B;}i0|^im)FRvN~d zIYKXM)`;mpm;Zt-jlh^q*313`o0a9#h0^@lUUpwYY(R7&{%#VlvDw|`f^VgI>4U0S zT$PzQ%IF-?=AaQt000C@0iIlHLSNO)rmkiO|KtfAqN%&sa4`j=(O1gD@Wibci=Z*3 zdDMMK;Whv(+w?f-4d>L&Cw5JqHs)$>Wv){$p96U^-~2R5?kkY~MK|c&t7On0S@i9m zfRvTLHDsKQGr3PtsHU=RiKlB`PRNN8u&M%_nW+5k+ufQ!Iz-b8qd9>+>tl5w(L@iy_F}KdllCRd%`5zRaIq#&9 zkY88xH+u6m@{im72+p%|jfUT5U5bcQN&svcQ+jkP1(<%4sz<+^2jotM8Y-XR8e1qP zDMTY0tidXD1(6Hpr%Mqg*bJ50|8$z`PI4MJ>hk|cD4I&6!~E1qKOC~_El;Y$g*e~S4)uUE5^n`OUmK*xqK^@13)X0O`pFC6m^PzW9H2^q z`RAsYfh8s_2Y4=MvcfKH(}Hx_joeHxryCdP{@-J2&lksB{~rJY%8-KX>43A-_9ZT=Y<6mB*?`B?rF(8I3#`}?E$e5;ZsDgPB(!B zOfiS&2ctlf+fgmr(1@G?D=3C*2J$>h)5@A|&AG@Er3QHi&1mGLZirq7GkH}IxrK>! zK(aIi1J_$~VAP|bsz24CB=gaenNxSN116!w9H%S99|hZgE^llkf*j8-Ova&@b5-`% zBlp{(xy5C%3Hr;hCSkbp4?d(1sg)|wB3&gsR)@$%LFgX9k#tLci4VL(reO1OiNCQ< zKpj|QV`Jj2FUbS1HzXeTw;UwaN-CVO^#NzPs%A~eGDX+(0{v#z5s>gjUTugf_vti- zbx%wbyq7d*X%;2I`;crT4MfE!g-QfPiDp$+#oi4-V}9{|+JXL6tMfPIh4#%epg z?rN{8p(5;+85k&~qehyWhb2O07d8s>*SU)>Nk07rXX7TjRK00#%3Grnrt*xI;M1& literal 0 HcmV?d00001 diff --git a/src/components/MobileFeed.tsx b/src/components/MobileFeed.tsx index 74e7a78..6db478d 100644 --- a/src/components/MobileFeed.tsx +++ b/src/components/MobileFeed.tsx @@ -76,18 +76,53 @@ function setRef(ref: Ref | undefined, value: T | null) { else if (ref) (ref as { current: T | null }).current = value; } +/** Aspect ratio (width / height) at or below which a video fills the slide (4:5 or taller). */ +export const COVER_MAX_RATIO = 0.8; + +/** "cover" for portrait and near-portrait video, "contain" for wider, "cover" while the size is unknown. */ +export function videoFit(width: number, height: number): "cover" | "contain" { + if (!(width > 0) || !(height > 0)) return "cover"; + return width / height <= COVER_MAX_RATIO ? "cover" : "contain"; +} + function SlideMedia({ media, author }: { media: CardMedia; author: string }) { const box = useRef(null); const [started, setStarted] = useState(false); + // Real video size once metadata loads; until then the thumbnail's size decides. + const [videoSize, setVideoSize] = useState<[number, number] | null>(null); + const [thumbSize, setThumbSize] = useState<[number, number] | null>(null); const playable = isPlayableVideo(media); + useEffect(() => { + const video = box.current?.querySelector("video"); + if (!video) return; + const read = () => { + if (video.videoWidth > 0 && video.videoHeight > 0) setVideoSize([video.videoWidth, video.videoHeight]); + }; + read(); + video.addEventListener("loadedmetadata", read); + return () => video.removeEventListener("loadedmetadata", read); + }, [playable, media.mp4Url, media.hlsUrl]); if (playable) { + const size = videoSize ?? thumbSize; + const fit = size ? videoFit(size[0], size[1]) : "cover"; const duration = formatDuration(media.durationSeconds); const start = () => { setStarted(true); void box.current?.querySelector("video")?.play().catch(() => {}); }; return ( -

+
+ {media.src ? ( + // The thumbnail doubles as the blurred backdrop behind a contained video, and as the size fallback. + // eslint-disable-next-line @next/next/no-img-element + setThumbSize([e.currentTarget.naturalWidth, e.currentTarget.naturalHeight])} + /> + ) : null}
diff --git a/src/dev/FeedPreview.tsx b/src/dev/FeedPreview.tsx index 2390611..a642f3b 100644 --- a/src/dev/FeedPreview.tsx +++ b/src/dev/FeedPreview.tsx @@ -9,19 +9,20 @@ import { DiscoverRailsView } from "@/components/DiscoverRails"; import { ExpertSearchView } from "@/components/ExpertSearch"; import { FeedView } from "@/components/FeedView"; import { toComments } from "@/lib/comment-model"; -import { toCardPosts, type CardPost } from "@/lib/feed-model"; +import { type CardPost } from "@/lib/feed-model"; import { FIXTURE_CATEGORIES, FIXTURE_COMMENTS, FIXTURE_DISCOVER, FIXTURE_FEED_ITEMS, + fixtureCards, FIXTURE_NOW, FIXTURE_PERSON, FIXTURE_SEARCH_RESULTS, } from "./fixtures"; export function FeedPreview() { - const [posts, setPosts] = useState(() => toCardPosts(FIXTURE_FEED_ITEMS)); + const [posts, setPosts] = useState(() => fixtureCards(FIXTURE_FEED_ITEMS)); const [draft, setDraft] = useState(""); const [file, setFile] = useState(null); const [query, setQuery] = useState(""); @@ -55,7 +56,7 @@ export function FeedPreview() { const it = raw as { post: { id: string } }; return { ...it, post: { ...it.post, id: `${it.post.id}-page${next}` } }; }); - setPosts((cur) => [...cur, ...toCardPosts(items)]); + setPosts((cur) => [...cur, ...fixtureCards(items)]); setPage(next); setPaging(false); }, 900); @@ -152,7 +153,7 @@ export function FeedPreview() { list.current?.scrollTo({ top: 0, behavior: "smooth" }); setRefreshing(true); window.setTimeout(() => { - setPosts(toCardPosts(FIXTURE_FEED_ITEMS)); + setPosts(fixtureCards(FIXTURE_FEED_ITEMS)); setRefreshing(false); }, 600); }} diff --git a/src/dev/fixtures.ts b/src/dev/fixtures.ts index 5763bfa..45364dc 100644 --- a/src/dev/fixtures.ts +++ b/src/dev/fixtures.ts @@ -2,21 +2,22 @@ // real: no Minnect person, post, token or URL. Images are inline SVG data URIs // so the preview never calls the network. Later tasks extend this file. import type { Person } from "@/lib/auth-client"; +import { toCardPosts, type CardPost } from "@/lib/feed-model"; import type { DiscoverCategory, DiscoverData, DiscoverExpert, DiscoverMinnect } from "@/lib/discover"; import type { ExpertResult } from "@/lib/experts-search"; /** Fixed "now" so relative times in the preview are stable. */ export const FIXTURE_NOW = Date.parse("2026-10-19T12:00:00Z"); -/** A 16:9 gradient placeholder image, drawn with hsl() so no fetch happens. */ -export function placeholderImage(hue: number, label = ""): string { +/** A gradient placeholder image (16:9 unless a size is given), drawn with hsl() so no fetch happens. */ +export function placeholderImage(hue: number, label = "", width = 1280, height = 720): string { const svg = - `` + + `` + `` + `` + - `` + + `` + (label - ? `${label}` + ? `${label}` : "") + ``; return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; @@ -25,6 +26,22 @@ export function placeholderImage(hue: number, label = ""): string { /** Not a real file: preload is off, so the preview video shows only its poster, play button and length. */ const FIXTURE_VIDEO_URL = "https://cdn.feed.sharedservices.vt.com/dev-fixture.mp4"; +/** Fixture videos with real (tiny) files in public/dev: the feed only accepts CDN-host mp4 URLs, so these are swapped to local paths after parsing. */ +const FIXTURE_LANDSCAPE_URL = "https://cdn.feed.sharedservices.vt.com/dev-landscape.mp4"; +const FIXTURE_PORTRAIT_URL = "https://cdn.feed.sharedservices.vt.com/dev-portrait.mp4"; +const LOCAL_FIXTURE_VIDEOS: Record = { + [FIXTURE_LANDSCAPE_URL]: "/dev/landscape.mp4", + [FIXTURE_PORTRAIT_URL]: "/dev/portrait.mp4", +}; + +/** toCardPosts for the preview, with the fixture video URLs pointed at the local files. */ +export function fixtureCards(items: unknown[]): CardPost[] { + return toCardPosts(items).map((p) => ({ + ...p, + media: p.media.map((m) => (m.mp4Url && LOCAL_FIXTURE_VIDEOS[m.mp4Url] ? { ...m, mp4Url: LOCAL_FIXTURE_VIDEOS[m.mp4Url] } : m)), + })); +} + /** Comments for the preview, in the shape /api/posts/[id]/comments returns. p2 holds a removed one; p4 has none. */ export const FIXTURE_COMMENTS: Record = { p1: [{ id: "c1", content: "Congratulations, the flow reads well.", createdAt: "2026-10-19T11:50:00Z", author: { displayName: "Priya Natarajan" } }], @@ -89,6 +106,30 @@ export const FIXTURE_FEED_ITEMS: unknown[] = [ mediaItems: [{ mediaType: "video", thumbnailUrl: placeholderImage(210, "Weekly reviews"), mp4Url: FIXTURE_VIDEO_URL, videoDuration: 154, order: 0 }], }, }, + { + type: "post", + publishedAt: "2026-10-19T07:50:00Z", + likesCount: 31, + commentsCount: 4, + post: { + id: "p8", + content: "Landscape recording of the quarterly walkthrough. It should show whole, centered, over a blurred copy of its own thumbnail.", + author: author("a8", "Tomas Reyes"), + mediaItems: [{ mediaType: "video", thumbnailUrl: placeholderImage(180, "Landscape 16:9"), mp4Url: FIXTURE_LANDSCAPE_URL, videoDuration: 2, order: 0 }], + }, + }, + { + type: "post", + publishedAt: "2026-10-19T07:20:00Z", + likesCount: 44, + commentsCount: 5, + post: { + id: "p9", + content: "Portrait clip shot on a phone. It should fill the whole slide.", + author: author("a9", "Ines Moreau"), + mediaItems: [{ mediaType: "video", thumbnailUrl: placeholderImage(300, "Portrait 9:16", 720, 1280), mp4Url: FIXTURE_PORTRAIT_URL, videoDuration: 2, order: 0 }], + }, + }, { type: "post", publishedAt: "2026-10-18T21:20:00Z", diff --git a/src/styles/mobile.css b/src/styles/mobile.css index cb905f7..faa747f 100644 --- a/src/styles/mobile.css +++ b/src/styles/mobile.css @@ -52,6 +52,22 @@ .slide-video .slide-art video, .slide-video .slide-art img { display: block; width: 100%; height: 100%; object-fit: cover; } .slide-video .slide-art > div { height: 100%; } +.slide-video .slide-art video { background: transparent; } +/* Wider than 4:5: show the whole video, centered, over a blurred, darkened, enlarged copy of its own thumbnail. */ +.slide-backdrop { display: none; } +.slide-video[data-fit="contain"] .slide-backdrop { + display: block; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + transform: scale(1.25); + filter: blur(28px) brightness(0.5); + pointer-events: none; +} +.slide-video[data-fit="contain"] .slide-art video, +.slide-video[data-fit="contain"] .slide-art img { object-fit: contain; } .slide-pending { display: grid; place-items: center; color: var(--ink-2); font-size: 14px; } .slide-play { z-index: 4; } .slide-dur { top: 128px; right: 16px; z-index: 4; } diff --git a/tests/mobile-feed.test.ts b/tests/mobile-feed.test.ts index 618ffae..3ed85c7 100644 --- a/tests/mobile-feed.test.ts +++ b/tests/mobile-feed.test.ts @@ -195,3 +195,14 @@ describe("top bar and progress rail", () => { expect(css).toMatch(/\.ph-top \{[^}]*safe-area-inset-top/); }); }); + +describe("videoFit", () => { + it("fills the slide for 4:5 and taller, shows whole for wider, covers while unknown", async () => { + const { videoFit } = await import("@/components/MobileFeed"); + expect(videoFit(720, 1280)).toBe("cover"); + expect(videoFit(1080, 1350)).toBe("cover"); + expect(videoFit(1080, 1080)).toBe("contain"); + expect(videoFit(1920, 1080)).toBe("contain"); + expect(videoFit(0, 0)).toBe("cover"); + }); +}); -- 2.54.0 From 6af225afa56caae62926429128ae11fc9e3df37c Mon Sep 17 00:00:00 2001 From: Cesar Alcazar Date: Tue, 20 Oct 2026 10:12:01 -0600 Subject: [PATCH 2/3] gs4f2wcln: Fit long text-only posts to the slide and add Read more and more/less for long post text on mobile --- src/app/globals.css | 2 + src/components/MobileFeed.tsx | 52 +++++++-- src/components/SlideText.tsx | 191 ++++++++++++++++++++++++++++++++++ src/dev/fixtures.ts | 52 +++++++++ src/styles/mobile.css | 149 +++++++++++++++++++++++--- tests/mobile-feed.test.ts | 59 ++++++++++- 6 files changed, 477 insertions(+), 28 deletions(-) create mode 100644 src/components/SlideText.tsx diff --git a/src/app/globals.css b/src/app/globals.css index f3c5b0f..cfb3f78 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -73,6 +73,8 @@ --media-fade-top: rgba(0,0,0,.45); --media-fade-0: rgba(0,0,0,0); --media-glass: rgba(0,0,0,.28); + /* Behind an open caption: darker than the fade, so long text reads over any photo. */ + --media-scrim: rgba(0,0,0,.72); --media-shadow: rgba(0,0,0,.35); /* HexAvatar initials: dark muted gradients, same in both themes. */ diff --git a/src/components/MobileFeed.tsx b/src/components/MobileFeed.tsx index 6db478d..6705096 100644 --- a/src/components/MobileFeed.tsx +++ b/src/components/MobileFeed.tsx @@ -6,6 +6,7 @@ import { GrowButton, SweepButton } from "./Buttons"; import { HexAvatar } from "./HexAvatar"; import { PlayIcon, PostMenu } from "./PostCard"; import { VideoPlayer } from "./VideoPlayer"; +import { CaptionText, QuoteText } from "./SlideText"; import { FeaturedCarousel } from "./FeaturedCarousel"; import { Logo } from "./Logo"; import { useTheme } from "@/lib/use-theme"; @@ -41,7 +42,7 @@ export function isDoubleTap(last: number | null, now: number, windowMs = DOUBLE_ export function isTapTarget(target: EventTarget | null): boolean { const el = target as Element | null; if (!el || typeof el.closest !== "function") return true; - return !el.closest("button, a, video, input, textarea, select, [role='menu'], [role='menuitem']"); + return !el.closest("button, a, video, input, textarea, select, [role='menu'], [role='menuitem'], [data-expanded-text]"); } /** Index of the featured experts slide among `postCount` posts, or -1 when it is not shown. */ @@ -185,13 +186,36 @@ function MediaTrack({ items, author }: { items: CardMedia[]; author: string }) { ); } -function PostSlide({ post, index, current, p }: { post: CardPost; index: number; current: boolean; p: FeedViewProps }) { +function PostSlide({ + post, + index, + current, + p, + onExpandedChange, +}: { + post: CardPost; + index: number; + current: boolean; + p: FeedViewProps; + onExpandedChange?: (index: number, open: boolean) => void; +}) { const items = post.media.filter((m) => m.kind === "video" || m.src); const media = items[0] ?? null; const own = canDelete(post, p.feedUserId) && !!p.onDelete; // While a video plays, the author and text step aside so the native controls stay clear. const [playing, setPlaying] = useState(false); - const cls = `slide ${media ? "slide-on-media" : "slide-on-text"}${current ? " is-cur" : ""}${playing ? " slide-playing" : ""}`; + // Long text opened with "Read more" or "more". It closes when the slide leaves the screen. + const [textOpen, setTextOpen] = useState(false); + useEffect(() => { + if (!current) setTextOpen(false); + }, [current]); + useEffect(() => { + onExpandedChange?.(index, textOpen); + }, [index, textOpen, onExpandedChange]); + useEffect(() => () => onExpandedChange?.(index, false), [index, onExpandedChange]); + const cls = + `slide ${media ? "slide-on-media" : "slide-on-text"}${current ? " is-cur" : ""}${playing ? " slide-playing" : ""}` + + `${own ? " slide-own" : ""}${textOpen ? " slide-text-open" : ""}`; const lastTap = useRef(null); const [burst, setBurst] = useState(0); useEffect(() => { @@ -227,9 +251,7 @@ function PostSlide({ post, index, current, p }: { post: CardPost; index: number;
- {media && post.text ?

{post.text}

: null} + {media && post.text ? : null}
); @@ -348,6 +370,13 @@ function LoadSlide({ index, current, sentinelRef }: { index: number; current: bo export function MobileFeed(p: FeedViewProps) { const reel = useRef(null); const [current, setCurrent] = useState(0); + // The slide whose long text is open: the feed holds still so swipes scroll the text. + const [expandedAt, setExpandedAt] = useState(null); + const locked = useRef(false); + locked.current = expandedAt !== null; + const onExpandedChange = useCallback((at: number, open: boolean) => { + setExpandedAt((cur) => (open ? at : cur === at ? null : cur)); + }, []); // The open sheet: the comments of one post, the composer, Discover (more), one of its panels, or none. const [sheet, setSheet] = useState<{ kind: "comments"; postId: string } | { kind: "composer" } | { kind: "more" } | { kind: "panel"; panel: DiscoverPanel } | null>(null); const closeSheet = useCallback(() => setSheet(null), []); @@ -401,6 +430,8 @@ export function MobileFeed(p: FeedViewProps) { let lastStep = 0; let from = 0; const onWheel = (e: WheelEvent) => { + // Open text scrolls natively; the reel is overflow-hidden meanwhile. + if (locked.current) return; if (e.ctrlKey || Math.abs(e.deltaY) < Math.abs(e.deltaX)) return; e.preventDefault(); const now = performance.now(); @@ -412,11 +443,11 @@ export function MobileFeed(p: FeedViewProps) { }; let startY: number | null = null; const onTouchStart = (e: TouchEvent) => { - startY = e.touches.length === 1 ? e.touches[0].clientY : null; + startY = e.touches.length === 1 && !locked.current ? e.touches[0].clientY : null; from = at(); }; const onTouchEnd = (e: TouchEvent) => { - if (startY === null || e.touches.length) return; + if (startY === null || e.touches.length || locked.current) return; const dy = startY - (e.changedTouches[0]?.clientY ?? startY); startY = null; if (Math.abs(dy) >= SWIPE_MIN) go(stepTarget(from, dy, count())); @@ -465,6 +496,7 @@ export function MobileFeed(p: FeedViewProps) { className="reel" data-testid="feed-reel" data-current={index} + data-locked={expandedAt !== null ? "true" : undefined} ref={(el) => { reel.current = el; setRef(p.listRef, el); @@ -483,7 +515,7 @@ export function MobileFeed(p: FeedViewProps) { ) : null} {p.status === "ready" ? p.posts.flatMap((post, i) => { - const node = ; + const node = ; return i === featuredAt - 1 ? [node, ] : [node]; diff --git a/src/components/SlideText.tsx b/src/components/SlideText.tsx new file mode 100644 index 0000000..95a9c4a --- /dev/null +++ b/src/components/SlideText.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; + +/** Quote sizes for a text-only post, largest first. The smallest one clamps and offers "Read more". */ +export const QUOTE_SIZES = [31, 26, 22, 19] as const; +/** Space kept under a clamped quote for the "Read more" button (44px tall plus its gap). */ +export const QUOTE_MORE_RESERVE = 52; + +export interface QuoteFit { + size: number; + /** Lines shown before "Read more"; 0 when the whole text fits. */ + lines: number; +} + +/** + * The first quote size whose rendered text fits, or the smallest size with as + * many whole lines as fit above the "Read more" button. `fits` measures the + * rendered text at a size; `linesAt` counts the lines that fit at the smallest one. + */ +export function pickQuoteFit(fits: (size: number) => boolean, linesAt: (size: number) => number): QuoteFit { + for (const size of QUOTE_SIZES) if (fits(size)) return { size, lines: 0 }; + const size = QUOTE_SIZES[QUOTE_SIZES.length - 1]; + return { size, lines: Math.max(1, Math.floor(linesAt(size))) }; +} + +/** Re-runs `measure` when the box resizes, web fonts finish loading, or the theme changes. */ +function useRemeasure(box: React.RefObject, measure: () => void, deps: unknown[]) { + useLayoutEffect(() => { + measure(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); + useEffect(() => { + const el = box.current; + if (!el) return; + let frame = 0; + const later = () => { + cancelAnimationFrame(frame); + frame = requestAnimationFrame(measure); + }; + const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(later) : null; + ro?.observe(el); + const fonts = typeof document !== "undefined" ? document.fonts : undefined; + void fonts?.ready.then(later); + fonts?.addEventListener?.("loadingdone", later); + const mo = typeof MutationObserver !== "undefined" ? new MutationObserver(later) : null; + mo?.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class"] }); + return () => { + cancelAnimationFrame(frame); + ro?.disconnect(); + mo?.disconnect(); + fonts?.removeEventListener?.("loadingdone", later); + }; + }, [box, measure]); +} + +/** + * Text-only post: the serif quote steps down 31 → 26 → 22 → 19px until it fits + * between the top bar and the author block. When it still does not fit, it + * clamps and ends with "Read more", which opens the full text over the slide. + */ +export function QuoteText({ text, open, onOpenChange }: { text: string; open: boolean; onOpenChange: (open: boolean) => void }) { + const box = useRef(null); + const inner = useRef(null); + const quote = useRef(null); + const moreBtn = useRef(null); + const closeBtn = useRef(null); + const [fit, setFit] = useState({ size: QUOTE_SIZES[0], lines: 0 }); + + const measure = useCallback(() => { + const b = box.current; + const q = quote.current; + const w = inner.current; + if (!b || !q || !w || !b.clientHeight) return; + const apply = (f: QuoteFit) => { + q.style.fontSize = `${f.size}px`; + q.style.webkitLineClamp = f.lines ? String(f.lines) : ""; + q.classList.toggle("is-clamped", f.lines > 0); + b.classList.toggle("is-clamped", f.lines > 0); + }; + const result = pickQuoteFit( + (size) => { + apply({ size, lines: 0 }); + return w.offsetHeight <= b.clientHeight + 0.5; + }, + () => { + // Height left for the text: the box minus the quote mark, its gap and the button. + const other = w.offsetHeight - q.offsetHeight; + const lineHeight = parseFloat(getComputedStyle(q).lineHeight) || 19 * 1.2; + return (b.clientHeight - other - QUOTE_MORE_RESERVE) / lineHeight; + }, + ); + apply(result); + setFit((cur) => (cur.size === result.size && cur.lines === result.lines ? cur : result)); + }, []); + useRemeasure(box, measure, [text]); + + // Focus follows the reader: into it on open, back to "Read more" on close. + const wasOpen = useRef(false); + useEffect(() => { + if (open) closeBtn.current?.focus({ preventScroll: true }); + else if (wasOpen.current) moreBtn.current?.focus({ preventScroll: true }); + wasOpen.current = open; + }, [open]); + + return ( + <> +
+
+ +

+ {text} +

+
+ {fit.lines ? ( + + ) : null} +
+ {open ? ( +
{ + if (e.key === "Escape") onOpenChange(false); + }} + > +
+

{text}

+
+ +
+ ) : null} + + ); +} + +/** + * Caption over a photo or video: three lines, then "more" when it is longer. + * Open, the whole caption shows over the media on a darker scrim, scrolling + * inside the slide when it is very long, with "less" to collapse it. + */ +export function CaptionText({ text, open, onOpenChange }: { text: string; open: boolean; onOpenChange: (open: boolean) => void }) { + const caption = useRef(null); + const [long, setLong] = useState(false); + const measure = useCallback(() => { + const c = caption.current; + // Measured only while clamped: an open caption is long by definition. + if (!c || c.classList.contains("is-open") || !c.clientHeight) return; + setLong(c.scrollHeight > c.clientHeight + 1); + }, []); + useRemeasure(caption, measure, [text]); + useEffect(() => { + if (!open && caption.current) caption.current.scrollTop = 0; + }, [open]); + return ( + <> +

+ {text} +

+ {long || open ? ( + + ) : null} + + ); +} diff --git a/src/dev/fixtures.ts b/src/dev/fixtures.ts index 45364dc..8d90146 100644 --- a/src/dev/fixtures.ts +++ b/src/dev/fixtures.ts @@ -81,6 +81,58 @@ export const FIXTURE_FEED_ITEMS: unknown[] = [ mediaItems: [], }, }, + { + // Medium text-only post: too long for 31px, fits at 26px or 22px with no "Read more". + type: "post", + publishedAt: "2026-10-19T11:30:00Z", + likesCount: 12, + commentsCount: 0, + post: { + id: "p10", + content: "The best advice I got as a new manager: your job is not to have the answers. It is to make sure the right question gets asked in the right room, by the person closest to the customer, early enough that the answer still changes what we build. Everything else, the rituals and the dashboards and the planning decks, exists to protect that one habit. When a meeting ends without a better question than it started with, it was a status update, and those can be an email.", + author: author("a10", "Grace Whitfield"), + mediaItems: [], + }, + }, + { + // Very long text-only post: does not fit at 19px, so it clamps and ends with "Read more". + type: "post", + publishedAt: "2026-10-19T11:10:00Z", + likesCount: 87, + commentsCount: 0, + post: { + id: "p11", + content: "I spent ten years building products before I learned the most useful habit of all: write down what you expect to happen before you ship, then check it a month later.\n\nMost teams skip the first half. They launch, watch a dashboard for a week and call whatever they see a win. Without the written guess there is nothing to be wrong about, so nobody learns.\n\nHere is the routine we use now. Every change gets one sentence: who it is for, what they will do differently, and the number that will move if we are right. The sentence goes in the ticket, not in a slide.\n\nThirty days later the person who wrote it reads it out loud in the weekly review and says what actually happened. No blame, no speeches. Just the guess, the result and one line on why they differ.\n\nAfter a quarter you get something no analytics tool gives you: a record of how good your team is at predicting its own customers. Ours was humbling. We were right about a third of the time.\n\nThat number changed how we plan. We now run smaller bets, we talk to five customers before any big build, and we kill features faster when the guess was wrong.\n\nIf you try one thing this month, try the sentence. It takes two minutes and it will teach you more than the next roadmap offsite.", + author: author("a11", "Daniel Moreau"), + mediaItems: [], + }, + }, + { + // Photo post with a caption longer than three lines: "more" / "less". + type: "post", + publishedAt: "2026-10-19T10:50:00Z", + likesCount: 64, + commentsCount: 0, + post: { + id: "p12", + content: "Notes from the panel on hiring a first sales lead, since a few people asked. Hire for the next eighteen months, not the next five years: the person who closes your first fifty customers is rarely the one who builds a team of twenty. Ask every candidate to walk you through a deal they lost and what they changed afterwards. Give them real numbers in the interview, including the bad ones, and watch how they react. Pay them on outcomes you can measure from day one, and write down what success looks like at thirty, sixty and ninety days. Last point: the founder keeps selling until the new lead has closed at least three deals alone. Handing over too early was the most common regret in the room.", + author: author("a12", "Aiko Tanaka"), + mediaItems: [{ mediaType: "image", imageUrl: placeholderImage(48, "Sales panel", 1080, 1350), order: 0 }], + }, + }, + { + // Video post with a long caption. + type: "post", + publishedAt: "2026-10-19T10:30:00Z", + likesCount: 39, + commentsCount: 0, + post: { + id: "p13", + content: "Full recording of the Thursday session on pricing experiments. Chapters: why we test price at all, how to pick the first segment, the three-week test plan, what to tell existing customers, reading the results without fooling yourself, and the questions from the audience at the end. Slides and the spreadsheet template are linked in the comments. If you only have five minutes, skip to the part on reading results: that is where most teams get it wrong, and where we lost two months last year before we noticed.", + author: author("a13", "Rafael Duarte"), + mediaItems: [{ mediaType: "video", thumbnailUrl: placeholderImage(230, "Pricing session"), mp4Url: FIXTURE_LANDSCAPE_URL, videoDuration: 2, order: 0 }], + }, + }, { type: "post", publishedAt: "2026-10-19T10:15:00Z", diff --git a/src/styles/mobile.css b/src/styles/mobile.css index faa747f..5ea5882 100644 --- a/src/styles/mobile.css +++ b/src/styles/mobile.css @@ -24,6 +24,8 @@ scrollbar-width: none; } .reel::-webkit-scrollbar { display: none; } +/* Long text is open on the slide on screen: the reel holds still, swipes scroll the text. */ +.reel[data-locked="true"] { overflow-y: hidden; } .slide { --slide-text-bg: radial-gradient(120% 70% at 0% 0%, var(--accent-soft), transparent 62%), var(--bg); @@ -154,14 +156,35 @@ .slide-error { margin: 0; color: var(--danger); font-size: 13px; font-weight: 600; } .slide-on-media .slide-error { color: var(--on-media); } -/* Text-only post: the text is the hero, a serif quote with a crimson opening mark. */ -.slide-quote-w { position: absolute; inset: 0; z-index: 2; pointer-events: none; } -.slide-quote { +/* Text-only post: the text is the hero, a serif quote with a crimson opening mark. + The box spans from under the top bar (or the own-post menu) to above the author + block; SlideText steps the size 31 → 26 → 22 → 19px until the text fits. */ +.slide-quote-w { position: absolute; + top: calc(72px + env(safe-area-inset-top, 0px)); + bottom: 181px; /* .slide-meta bottom (108px) + the author block (53px) + 20px */ left: 24px; right: 28px; - top: 46%; - transform: translateY(-50%); + z-index: 2; + display: flex; + flex-direction: column; + overflow: hidden; + pointer-events: none; +} +.slide-own .slide-quote-w { top: calc(122px + env(safe-area-inset-top, 0px)); } +/* Auto margins center the quote and, unlike justify-content, never push overflow above the box. */ +.slide-quote-in { margin: auto 0; } +.slide-quote-w.is-clamped .slide-quote-in { margin-top: 0; } +.slide-quote-mark { + display: block; + margin-bottom: 6px; + color: var(--accent); + font-family: var(--font-serif), Georgia, serif; + font-size: 84px; + font-weight: 600; + line-height: .62; +} +.slide-quote { margin: 0; font-family: var(--font-serif), Georgia, serif; font-size: 31px; @@ -169,19 +192,114 @@ line-height: 1.2; white-space: pre-wrap; overflow-wrap: anywhere; +} +.slide-quote.is-clamped { display: -webkit-box; - -webkit-line-clamp: 8; -webkit-box-orient: vertical; overflow: hidden; } -.slide-quote::before { - content: "\201C"; - display: block; - margin-bottom: 6px; - color: var(--accent); - font-size: 84px; - line-height: .62; + +/* "Read more", "Close", "more" and "less": real buttons, 44px tall to tap. */ +.slide-more { + appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + margin: 0; + padding: 0 18px; + border: 0; + border-radius: 14px; + font: inherit; + font-size: 14px; + font-weight: 700; + letter-spacing: .01em; + color: var(--ink); + background: var(--glass); + -webkit-backdrop-filter: blur(16px) saturate(1.6); + backdrop-filter: blur(16px) saturate(1.6); + box-shadow: inset 0 0 0 1px var(--line); + cursor: pointer; + pointer-events: auto; + transition: transform .15s; } +.slide-more:active { transform: scale(.96); } +.slide-more:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } +.slide-more-quote { position: absolute; left: 0; bottom: 0; } + +/* The full text of a long text-only post, over the slide between the top bar and the dock. */ +@keyframes slide-reader-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } +.slide-reader { + position: absolute; + top: calc(64px + env(safe-area-inset-top, 0px)); + bottom: calc(88px + env(safe-area-inset-bottom, 0px)); + left: 12px; + right: 12px; + z-index: 7; + display: flex; + flex-direction: column; + gap: 10px; + padding: 20px 14px 14px 20px; + border-radius: 22px; + color: var(--ink); + background: var(--bg); + box-shadow: var(--shadow-pop), inset 0 0 0 1px var(--line); + animation: slide-reader-in .28s var(--ease) both; +} +.slide-reader-scroll { + flex: 1; + min-height: 0; + overflow-y: auto; + overscroll-behavior: contain; + padding-right: 6px; +} +.slide-reader-scroll:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 6px; } +.slide-reader-text { + margin: 0; + font-family: var(--font-serif), Georgia, serif; + font-size: 22px; + font-weight: 600; + line-height: 1.35; + white-space: pre-wrap; + overflow-wrap: anywhere; +} +.slide-reader-close { flex: none; align-self: flex-end; } + +/* Caption "more" / "less": a text button right under the three-line clamp. */ +.slide-more.slide-more-caption { + align-self: flex-start; + flex: none; + margin: -12px 0 -10px -6px; + padding: 0 6px; + min-width: 44px; + background: none; + -webkit-backdrop-filter: none; + backdrop-filter: none; + box-shadow: none; + color: inherit; + text-decoration: underline; + text-underline-offset: 3px; + text-shadow: 0 1px 12px var(--media-shadow); +} +/* Open caption: the full text over the media on a darker scrim, scrolling inside the slide. */ +.slide-who, +.slide-error { flex: none; } +.slide-text-open .slide-meta { + top: calc(122px + env(safe-area-inset-top, 0px)); + justify-content: flex-end; +} +.slide-text.is-open { + display: block; + -webkit-line-clamp: unset; + flex: 0 1 auto; + min-height: 0; + overflow-y: auto; + overscroll-behavior: contain; + animation: slide-reader-in .28s var(--ease) both; +} +.slide-text.is-open:focus-visible { outline: 2px solid var(--on-media); outline-offset: 4px; } +.slide-text-open .slide-scrim { height: 100%; background: var(--media-scrim); } +.slide-text-open .slide-scrim-top { opacity: 0; } /* The slide on screen: its content rises in when it snaps into place. */ .is-cur .slide-meta, @@ -391,10 +509,13 @@ .is-cur .slide-quote-w, .slide-skel i, .slide-burst, + .slide-reader, + .slide-text.is-open, .slide-hint svg, .reel-pill, .dk-beat svg { animation: none !important; } - .slide-art { transition: none !important; } + .slide-art, + .slide-more { transition: none !important; } } /* Top bar (spec "Top bar"): frosted pills over the feed. Over a media slide they use a diff --git a/tests/mobile-feed.test.ts b/tests/mobile-feed.test.ts index 3ed85c7..0330d53 100644 --- a/tests/mobile-feed.test.ts +++ b/tests/mobile-feed.test.ts @@ -7,6 +7,7 @@ import { pickCurrent, isDoubleTap, isTapTarget, featuredSlideIndex, railPercent import { FIXTURE_DISCOVER } from "@/dev/fixtures"; import { CompactLayoutContext, isCompactWidth } from "@/components/layout-context"; import { toCardPosts } from "@/lib/feed-model"; +import { pickQuoteFit, QUOTE_SIZES } from "@/components/SlideText"; const NOW = Date.parse("2026-10-19T12:00:00Z"); const item = (id: string, authorId: string, mediaItems: unknown[] = []) => ({ @@ -50,10 +51,10 @@ describe("mobile feed", () => { it("renders one slide per post, text as a quote and media under the fades", () => { const html = render({}); expect(html.match(/data-testid="feed-slide"/g)).toHaveLength(3); - expect(html).toContain('class="slide-quote">text t1

'); + expect(html).toMatch(/class="slide-quote" data-fit-size="31" style="font-size:31px">text t1<\/p>/); expect(html).toMatch(/slide slide-on-media[^"]*"[^>]*aria-label="Post by Author i1"/); expect(html).toContain("slide-scrim-top"); - expect(html).toContain('class="slide-text">text i1

'); + expect(html).toMatch(/class="slide-text"[^>]*>text i1<\/p>/); expect(html).toContain('aria-label="Play video"'); // The quote slide shows its text once, as the quote. expect(html.match(/text t1/g)).toHaveLength(1); @@ -61,7 +62,7 @@ describe("mobile feed", () => { it("marks the first slide as the one on screen", () => { const html = render({}); - expect(html).toMatch(/class="slide slide-on-text is-cur"/); + expect(html).toMatch(/class="slide slide-on-text is-cur slide-own"/); expect(html).toContain('data-current="0"'); }); @@ -101,7 +102,7 @@ describe("mobile stylesheet", () => { }); it("uses a 31px serif quote with a crimson mark, and eases media from 104%", () => { expect(css).toMatch(/\.slide-quote \{[^}]*font-size: 31px/); - expect(css).toMatch(/\.slide-quote::before \{[^}]*color: var\(--accent\)/); + expect(css).toMatch(/\.slide-quote-mark \{[^}]*color: var\(--accent\)/); expect(css).toMatch(/transform: scale\(1\.04\)/); expect(css).toMatch(/\.is-cur \.slide-art \{ transform: scale\(1\); \}/); }); @@ -206,3 +207,53 @@ describe("videoFit", () => { expect(videoFit(0, 0)).toBe("cover"); }); }); + +describe("long post text", () => { + it("picks the largest quote size that fits, measured, not counted", () => { + const fitsBelow = (max: number) => (size: number) => size <= max; + expect(pickQuoteFit(fitsBelow(40), () => 0)).toEqual({ size: 31, lines: 0 }); + expect(pickQuoteFit(fitsBelow(26), () => 0)).toEqual({ size: 26, lines: 0 }); + expect(pickQuoteFit(fitsBelow(22), () => 0)).toEqual({ size: 22, lines: 0 }); + expect(pickQuoteFit(fitsBelow(19), () => 0)).toEqual({ size: 19, lines: 0 }); + expect([...QUOTE_SIZES]).toEqual([31, 26, 22, 19]); + }); + + it("clamps at 19px to the whole lines that fit when nothing fits", () => { + const tried: number[] = []; + const fit = pickQuoteFit((s) => (tried.push(s), false), () => 17.8); + expect(tried).toEqual([31, 26, 22, 19]); + expect(fit).toEqual({ size: 19, lines: 17 }); + expect(pickQuoteFit(() => false, () => 0.2)).toEqual({ size: 19, lines: 1 }); + }); + + it("shows no button on short text until a measurement says it is long", () => { + const html = render({}); + expect(html).not.toContain("Read more"); + expect(html).not.toMatch(/>more<\/button>/); + expect(html).not.toContain("slide-reader"); + }); + + it("keeps taps on open text from counting toward a double-tap like", () => { + const el = { closest: (sel: string) => (sel.includes("[data-expanded-text]") ? {} : null) } as unknown as Element; + expect(isTapTarget(el)).toBe(false); + }); +}); + +describe("long post text stylesheet", () => { + const css = readFileSync("src/styles/mobile.css", "utf8"); + it("fits the quote between the top bar and the author block", () => { + expect(css).toMatch(/\.slide-quote-w \{[^}]*top: calc\(72px[^}]*bottom: 181px[^}]*overflow: hidden/); + }); + it("makes the buttons at least 44px tall", () => { + expect(css).toMatch(/\.slide-more \{[^}]*min-height: 44px/); + }); + it("holds the reel still while text is open, and the open text scrolls", () => { + expect(css).toMatch(/\.reel\[data-locked="true"\] \{ overflow-y: hidden; \}/); + expect(css).toMatch(/\.slide-reader-scroll \{[^}]*overflow-y: auto/); + expect(css).toMatch(/\.slide-text\.is-open \{[^}]*overflow-y: auto/); + expect(css).toMatch(/\.slide-text-open \.slide-scrim \{[^}]*var\(--media-scrim\)/); + }); + it("drops the expand animation under reduced motion", () => { + expect(css).toMatch(/prefers-reduced-motion: reduce\) \{[^}]*\.slide-reader,\s*\.slide-text\.is-open,/); + }); +}); -- 2.54.0 From e37ec780a95e0dfbdd4a77463cfaa8047a0900ff Mon Sep 17 00:00:00 2001 From: Cesar Alcazar Date: Tue, 20 Oct 2026 10:22:39 -0600 Subject: [PATCH 3/3] o2buvioma: Autoplay mobile feed videos muted when they snap into view, with a session mute toggle and tap to pause --- src/components/MobileFeed.tsx | 173 +++++++++++++++++++++++++++++---- src/components/VideoPlayer.tsx | 7 +- src/lib/autoplay.ts | 24 +++++ src/styles/mobile.css | 26 +++++ tests/autoplay.test.ts | 46 +++++++++ 5 files changed, 254 insertions(+), 22 deletions(-) create mode 100644 src/lib/autoplay.ts create mode 100644 tests/autoplay.test.ts diff --git a/src/components/MobileFeed.tsx b/src/components/MobileFeed.tsx index 6705096..c9cf5d6 100644 --- a/src/components/MobileFeed.tsx +++ b/src/components/MobileFeed.tsx @@ -10,6 +10,7 @@ import { CaptionText, QuoteText } from "./SlideText"; import { FeaturedCarousel } from "./FeaturedCarousel"; import { Logo } from "./Logo"; import { useTheme } from "@/lib/use-theme"; +import { getSessionMuted, isAutoplayBlocked, setSessionMuted } from "@/lib/autoplay"; import { MobileDock } from "./MobileDock"; import { BottomSheet } from "./BottomSheet"; import { CommentsSection } from "./CommentsSection"; @@ -86,33 +87,137 @@ export function videoFit(width: number, height: number): "cover" | "contain" { return width / height <= COVER_MAX_RATIO ? "cover" : "contain"; } -function SlideMedia({ media, author }: { media: CardMedia; author: string }) { +/** True when the person asked for less motion. Updates when the setting changes; false on the server. */ +function useReducedMotion(): boolean | null { + const [reduced, setReduced] = useState(null); + useEffect(() => { + if (typeof window.matchMedia !== "function") return setReduced(false); + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + const read = () => setReduced(mq.matches); + read(); + mq.addEventListener?.("change", read); + return () => mq.removeEventListener?.("change", read); + }, []); + return reduced; +} + +function SoundIcon({ muted }: { muted: boolean }) { + return ( + + ); +} + +/** + * One media item of a slide. A video plays by itself, muted and inline, while + * `active` (its slide and pane are on screen) unless the person asked for less + * motion or the browser refuses: then the play button starts it as before. + * `hold` (a sheet is open) pauses a playing video and resumes it after. + */ +function SlideMedia({ media, author, active = false, hold = false }: { media: CardMedia; author: string; active?: boolean; hold?: boolean }) { const box = useRef(null); const [started, setStarted] = useState(false); // Real video size once metadata loads; until then the thumbnail's size decides. const [videoSize, setVideoSize] = useState<[number, number] | null>(null); const [thumbSize, setThumbSize] = useState<[number, number] | null>(null); const playable = isPlayableVideo(media); + const reduced = useReducedMotion(); + // The browser refused autoplay: fall back to the play button. + const [blocked, setBlocked] = useState(false); + const auto = reduced === false && !blocked; + const [muted, setMuted] = useState(getSessionMuted); + // Paused by a tap: it stays paused until tapped again or the slide leaves. + const [userPaused, setUserPaused] = useState(false); + const userPausedRef = useRef(false); + const resumeAfterHold = useRef(false); + const video = () => box.current?.querySelector("video") ?? null; useEffect(() => { - const video = box.current?.querySelector("video"); - if (!video) return; + const v = video(); + if (!v) return; const read = () => { - if (video.videoWidth > 0 && video.videoHeight > 0) setVideoSize([video.videoWidth, video.videoHeight]); + if (v.videoWidth > 0 && v.videoHeight > 0) setVideoSize([v.videoWidth, v.videoHeight]); }; read(); - video.addEventListener("loadedmetadata", read); - return () => video.removeEventListener("loadedmetadata", read); + v.addEventListener("loadedmetadata", read); + return () => v.removeEventListener("loadedmetadata", read); }, [playable, media.mp4Url, media.hlsUrl]); + + const autoplay = useCallback((v: HTMLVideoElement) => { + v.muted = getSessionMuted(); + // Like a reel, an autoplayed video loops while it is on screen. + v.loop = true; + setMuted(v.muted); + v.play().catch((err: unknown) => { + if (!isAutoplayBlocked(err)) return; + // Sound not allowed yet: play muted this time, keeping the session's choice. + if (!v.muted) { + v.muted = true; + setMuted(true); + v.play().catch((again: unknown) => { + if (isAutoplayBlocked(again)) setBlocked(true); + }); + } else setBlocked(true); + }); + }, []); + + useEffect(() => { + const v = video(); + if (!v || !playable) return; + if (!active) { + userPausedRef.current = false; + setUserPaused(false); + resumeAfterHold.current = false; + v.pause(); + return; + } + if (hold) { + if (!v.paused) { + resumeAfterHold.current = true; + v.pause(); + } + return; + } + if (resumeAfterHold.current) { + resumeAfterHold.current = false; + void v.play().catch(() => {}); + return; + } + if (auto && !userPausedRef.current) autoplay(v); + }, [active, hold, auto, playable, autoplay]); + if (playable) { const size = videoSize ?? thumbSize; const fit = size ? videoFit(size[0], size[1]) : "cover"; const duration = formatDuration(media.durationSeconds); const start = () => { setStarted(true); - void box.current?.querySelector("video")?.play().catch(() => {}); + const v = video(); + if (!v) return; + v.muted = false; + void v.play().catch(() => {}); }; + // Autoplay: a tap on the video pauses or resumes it (no native controls). + const togglePause = () => { + const v = video(); + if (!v) return; + const pause = !v.paused; + userPausedRef.current = pause; + setUserPaused(pause); + if (pause) v.pause(); + else void v.play().catch(() => {}); + }; + const toggleMute = () => { + const v = video(); + const next = !muted; + if (v) v.muted = next; + setSessionMuted(next); + setMuted(next); + }; + const showPlay = auto ? userPaused : !started; return ( -
+
{media.src ? ( // The thumbnail doubles as the blurred backdrop behind a contained video, and as the size fallback. // eslint-disable-next-line @next/next/no-img-element @@ -124,17 +229,36 @@ function SlideMedia({ media, author }: { media: CardMedia; author: string }) { onLoad={(e) => setThumbSize([e.currentTarget.naturalWidth, e.currentTarget.naturalHeight])} /> ) : null} -
- +
+
- {started ? null : ( + {auto ? ( + + ) : null} + {showPlay ? ( <> - - {duration ? {duration} : null} + {duration && !auto ? {duration} : null} - )} + ) : null}
); } @@ -154,7 +278,7 @@ function SlideMedia({ media, author }: { media: CardMedia; author: string }) { } /** Several media items in one slide: a sideways scroll-snap track with a dot per item. */ -function MediaTrack({ items, author }: { items: CardMedia[]; author: string }) { +function MediaTrack({ items, author, current, hold }: { items: CardMedia[]; author: string; current: boolean; hold: boolean }) { const track = useRef(null); const [active, setActive] = useState(0); const onScroll = () => { @@ -173,7 +297,7 @@ function MediaTrack({ items, author }: { items: CardMedia[]; author: string }) {
{items.map((m, i) => (
- +
))}
@@ -190,19 +314,22 @@ function PostSlide({ post, index, current, + hold, p, onExpandedChange, }: { post: CardPost; index: number; current: boolean; + /** A sheet is open over the feed: a playing video pauses until it closes. */ + hold: boolean; p: FeedViewProps; onExpandedChange?: (index: number, open: boolean) => void; }) { const items = post.media.filter((m) => m.kind === "video" || m.src); const media = items[0] ?? null; const own = canDelete(post, p.feedUserId) && !!p.onDelete; - // While a video plays, the author and text step aside so the native controls stay clear. + // While a video plays with native controls (play button, not autoplay), the author and text step aside. const [playing, setPlaying] = useState(false); // Long text opened with "Read more" or "more". It closes when the slide leaves the screen. const [textOpen, setTextOpen] = useState(false); @@ -240,13 +367,19 @@ function PostSlide({ data-liked={post.isLiked ? "true" : "false"} onClick={onTap} aria-label={`Post by ${post.authorName}`} - onPlayCapture={() => setPlaying(true)} + onPlayCapture={(e) => { + if ((e.target as HTMLVideoElement).controls) setPlaying(true); + }} onPauseCapture={() => setPlaying(false)} onEndedCapture={() => setPlaying(false)} > {media ? ( <> - {items.length > 1 ? : } + {items.length > 1 ? ( + + ) : ( + + )}