From 1aeb9072d470e6d33f3de41bc455eaff7cfd8803 Mon Sep 17 00:00:00 2001 From: boomzero Date: Sat, 3 Oct 2026 23:27:06 +0800 Subject: [PATCH 1/5] fix: use consistent Chinese navbar fallback fonts --- XMOJ.user.js | 6 +- tests/fixtures/fonts/OFL.txt | 93 ++++++++++++++++++ tests/fixtures/fonts/README.md | 10 ++ .../fixtures/fonts/source-serif-4-latin.woff2 | Bin 0 -> 20112 bytes tests/navbar-styler.test.cjs | 37 +++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/fonts/OFL.txt create mode 100644 tests/fixtures/fonts/README.md create mode 100644 tests/fixtures/fonts/source-serif-4-latin.woff2 diff --git a/XMOJ.user.js b/XMOJ.user.js index 745489f9..3a5d84df 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -143,6 +143,9 @@ const MonochromeSkinCSS = ` --mono-border-thin: 1px solid var(--mono-gray-300); --mono-font-heading: 'Playfair Display', Georgia, serif; --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; + /* Source Serif has no Chinese glyphs. An explicit navbar fallback + avoids different fonts under legacy lang=en and /web lang=zh-CN. */ + --mono-font-navbar: 'Source Serif 4', 'Source Serif Pro', Georgia, 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', serif; --mono-font-mono: 'JetBrains Mono', 'Consolas', monospace; --mono-transition: 100ms ease; } @@ -197,6 +200,7 @@ const MonochromeSkinCSS = ` /* Navbar */ .navbar, nav.navbar { + font-family: var(--mono-font-navbar) !important; border-bottom: 4px solid var(--mono-black) !important; background-color: var(--mono-white) !important; opacity: 1 !important; @@ -204,7 +208,7 @@ const MonochromeSkinCSS = ` .navbar .nav-link { color: var(--mono-black) !important; text-decoration: none !important; - font-family: var(--mono-font-body) !important; + font-family: var(--mono-font-navbar) !important; text-transform: uppercase !important; letter-spacing: 0.05em !important; font-size: 0.85rem !important; diff --git a/tests/fixtures/fonts/OFL.txt b/tests/fixtures/fonts/OFL.txt new file mode 100644 index 00000000..318074ec --- /dev/null +++ b/tests/fixtures/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014 The Source Serif 4 Project Authors (https://github.com/adobe-fonts/source-serif) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/tests/fixtures/fonts/README.md b/tests/fixtures/fonts/README.md new file mode 100644 index 00000000..b4de304b --- /dev/null +++ b/tests/fixtures/fonts/README.md @@ -0,0 +1,10 @@ +This is the unmodified Latin subset of Source Serif 4 (regular, v15) served by +Google Fonts, the same font used by the monochrome theme. It is distributed under +the SIL Open Font License in OFL.txt. + +Source: https://fonts.gstatic.com/s/sourceserif4/v15/vEFy2_tTDB4M7-auWDN0ahZJW3IX2ih5nk3AucvUHf6OAVIJmeUDygwjihdqnhtdDs4DrjTMXw.woff2 +License: https://github.com/google/fonts/blob/main/ofl/sourceserif4/OFL.txt + +The navbar rendering regression embeds this 20 KB font offline. A local font or +an unloaded font does not reproduce Chromium's language-dependent Chinese +fallback for this webfont; checking computed font-family alone also misses it. diff --git a/tests/fixtures/fonts/source-serif-4-latin.woff2 b/tests/fixtures/fonts/source-serif-4-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..12f5b101b9d3f0b18ecdc0e26b61c8d4875fbffb GIT binary patch literal 20112 zcmV({K+?Z=Pew8T0RR9108Wqq5C8xG0MU>D08T6b0RR9100000000000000000000 z0000PMjCV*fuT|ykpKo@KT}jeR9*mtS`i2efzM=tr#B0Q7ytn_0we>9ECe70h6)ED z8?w0-%54{MJMeJ1EPEMjoAho6m1XmtxlvJ0Qx7Ej|6>9jG6be+VN?I0kU~lzw@s4m zVCdx7r(=iPhLK|{Y?1ahbyrWkjw!S&ri6G3sif;OtB3Mo*<1nrMbG`oq9n8a%j8zl zHRqqdTY3EmjeU|!^8E99&$sqT9=-;ot7%<9B~>(2!N~o2ew%;JeZk{VF+@}%jYkZY z=vVAmxr}n%E-QZ5@ci6*FxY^}0@f`4s{a1QUleT3Mr|Wn=uxGIiP(4MiFE;1UCEVa z#p&s#SJv5aa*+@PhuG|1ieLi@c*3E8welS+>Z3kEjv`prs%P&$$_S{a5YB_q2#HcS zBr@c=`rquo2d+-Ty-(mE76D%~h^@;Aa0p43Y0fJEZa|0t39PkNvDCAflvbt zm;zn_d>oi^E)?L?CnqEn5*q(zO^rzFCSMd=PHPR22jqvh0{Dkaw~Y^mM<}c&OyUq2 z)wAs|ogVkQ9DmKX5z#sl5h36PV`~ht%I95{($&qIcYzVW|b61h894Gv8WS zBlnHHu05T#lQiem?Chkp&I_ysprBHDX{<9EfG$SC=`;veKzaV&X4?Lhbjj*u(uH&Z zfTAO)2&(Gu+m)6C>)j)*46@F!k__^i6q%$w#U~knssMoQ&OjVsY_D?CB~?;pQdD-+ z#bv6}RS1isN!O)}|FyKFdh_++|(Xg@_y@H_90rU=y^M8vtbA_Ga<~mUeG2rNl#6N5L{Z?!Vso z{y+bF+M_+M(q*Y-TdE?WB1TTc$Q@(EsP4z#J;u~}NZMa3U1kwQT!^TrfS~dBU#0+4 zU;{LIhn;c?f$?@)Z#ytP_BOTw<6CWiCop0Gf{@}Mkajq7>Iscqy31`Dghmd2!izB`&qp3chERA5_MohCZ~dR z>@|NZ&iI%w+}008(wjiqdu0(s<_2bt3Y^+b}B8Rx~|LEWJT*IBrSud7N3aH4tAC=X646PW8^9{I1k@4?qHu&d4I+ z#inbhoE}#}ki=2opGGnKtO={&^H(}!(VCHQ)1k)Ci)zj!nk>?$5v?1Ks~T#qtI=TT zF}CYFM7W|nQwPqqKMV>aEw7o61(zfq7K=@H_kf1KoCqgtRjV2CF6t#>&Mu|aAMtVF zgS2HOzksLqcJWqYG(QbHH_^{eA^_EKmblYRew_%p;N(-SrO}BU+X~^keB88=0Ivm4 zc2uz#7t!QxmNieU$CfEz4m&uWyv;E5S4Sf~igg8Qk#F!U{ZdE$v|!n)S*p7ac{I_6 zpLEUwtt``;RoXNgSMTl$Bp7jVv!BMx^S`w@zZt66LoETXp6T#Y|*cjDwMWqLpz7P8qD1O~=HbrnUUp_1+4^7=b>a#8Ig^M8E-K5BAsDrLp zBj6x(EtZ1p5GT1OkhYi{BVF@jr4)mklLb0t_#-P-sz2Y$F~Jbwo70i2A<{Qov8U58 zD%4E3QCI}C`PI=#vYOPl>BHkNFOO!**g>>ab{RRNW3E(Jt03a@)uqF{roH~u+0>f9 z#`9D&)a6l<^!szAZG)cdfq|ikX{l-H>8Z)GIS)Kg85pP=8fu!DXqcKxRa&w#;qA%( zP+I4!-rU7O8HA8^O0NhOY^x)PLwj^T^ocdqkLALN@AB#7tzoq&15x}oW@GS zqQE=t2&W5YkXdei`J1jgJjCx^&r}V`g+h5`=#f%k|8A~qsd+)e^=zq>GNcj3u$pp{ zFMs5->l#K4Y8j6n{5jF!rT%g+DTmz7ks6vjniegx+j_URKBceNSEC{~ClyCRUQugl zt0=k8SE*$UXc$H7NPA`&wl}QmGbrhrWoS826RTCUBq5yQ(5OFK*vj(j1Gcq(rJ8YM zWo{`AVa`zk9zWRPF?-~2X%@b5jtC?bFax+e=C(iI|B#@F%roC&OKrEKIFzFb!J$xC zA|jl5<{{>rkFnTdw5689;5jI{0SzK)88MSR)Xa_Ev4J9*>1~ISi!e0$kjwIM1iH(P z4?Ae)*@T@uExi+rBw$g1!vRjk=%zr4EYR-h#g(tMm^h`ffK@b+Jw>aiwYYKXF_L&9 ztByn2^_L!7w*(=oYN%q(rV(_>qphX8c{6y%()1YSBCjlN1!t}?0MfTBi8FhcQ%(#M z5AK!+c}&+I=;7qhf+ccVXC8yz&0+e6G7bZS%vkhyW#2Tb$y!zU`+;4sL&{X_HWg&4 zW_B66<@UR|f!|Uc%;?u8TW04_wn_s`tss=ru^`mJ6{4JU`Nx}SW{ts~b{YI;kmW#^XYWM}tFQ}H$B$Wa z9wT#L=D|rC(;c)?A1cUhg2;-&W6DBB<--iB$F&4B(RN_&E6AScZ(Ns@Q+HobrV1A2 z!y=C@8v-QxCQ(!~@BZqgQTgp)?&G@1zX;WNEXWYF%41!++Y_CrkWTHu*DJ}JC#^?Q zF{XC}O&N6aiT3k%Y7@-z!VA#&^twKMl^Y!3Af)acrCXsz@a$ay4qm zEV2k2OG#%*$;QoSTM}h#rL=97v7btuq`cFVcZP~x2*q|tv~TZEPMSd=mfG#*q3&f^ zCyk$#!W_CkxQ5lanKqkPg`wtt+i38@ zou3BjpuI!Ls4q|dW%uq<#opK=ke!ikCP_E5Yu1)RQAB$u4Am}XRgg2qrJM(XmYhLo zClVcCBi&@)sR^#So0gLjH-?J4x;S>ffxQpd&b?aVrjLZdI9N`{cL!l!P zEu@pq7Fc4LP&1X-U8W<3 z>E~P95sIQ>T}F5R^biQrT~)E;We8H6(&A=j#ksGI3!eUn#Yvm7Btii036UxuqY($C zjF7Y7e_opve5AAdAk0Bx;~>1j6h*n?h$EGtxu@~(2$ZRt%_SOxp=5(LuLd_lbjP)6ir+_)kI?yQV9i`bK9zS z?a^x%-_el8Zd~OFv|Tyq0F*uw7omM^8&#HaU!r6cn6-#Ln9!p!G;|DNIV2GztI$$+ zql|aRVMiQw%<;Sk9XgmjC`&_;j*1wlL@ebwC?$!~k}0EtvRYDJcS;#WDdQ>S5ak@E zVn-1+KImg@LWTu9C88O%P1I_#2%jiiGi`X=n`-C#@jQ#aJ8=)$^%vicBSJ<|P zl&oZc&FsGg$J1Tg8h%2|$}mUSa^HXKs6xhiSvT8^u&dk^my$^7yHMmqq>ywL!Hfr2 z4tEc@m@G3J7XiH6BmbK2eaCOg-cc`OyrK9>dp4KDU{);tcC6~jtpqJ6-Cb%R=ja~4 z${EycsurAW+L04a7aCg|o88BJ98Tc7JaPj*&_GU4>v!4B~qdsg}a03S=^Y zi#(iwO8XQdy$$D%Q2@;CCC&V|soUDP9C(b1$kq#Q6K*+eS z=rz2tfka_YmRDM9CmGrf1|K6!g2PxIti-~$=4HEc$O9$Jz?hZs92_w^*yKP=xXT5JQ z*#uQSsjZ94nMLLG&&ufG3uh zD=sx}2)!t`C0j$hGGT0S)8A@ZkmxOInn?*~+gdJ4R)@Sfm_25;#~iH1_U|K&)e5GC zT?sCvXiZz$r2L2DwGXRpf3x$Ybk0RSWFvZaaWRo9Mp&!zaSE(qvicY*d^Y^JX6@o| z()09dQec|pJdF;fmV6TAJw=eL*HT57;3Xi)PP{3FNK-dt`I;1ZEkRlO)D)Tvnlpbn z8B1F67#jbrw&g8G2)yow)&i^twi11cyN&#KIZZc`0W)lnChvc@qoqV62r$M%93CN~ z#86Wc)6(MU=}8zFNtwiuiDRZEUYdctC*DCbb6T4Swr6wyUDd3xxt z&_Kgf8eyasMjNB0amHzFf{EJLW1n{Z^0!6^lc|^pq9A;!q-m>!1a*9AwK`}iOunKN zgUN@wRr{%)qf8XMX^AH>zJytZ%xSWcaC<`aUETBk@ZxY%lq0LMn&_IhgXfuur z!45DidMmF0s&fDFgo z+E@<}suL+?Xyn=?G|V>3Lvk#V(?a`WKD zOF;8FqvA`7`2|P>lM?kbMhPbwDbg?^NwsuYSlLFQD9I>RhEtwYs?q{ceK{hnv?bC` zdpaF-Bx_X2QY3jj^+W65PGq1V#D*G1VuUlakw#{;ks4!E#u=FjMrxv}nwj!>F*cb& z%})kandjZ{nQ<&U3cFxwGym4zUTf_vp7KJJ{zuR5!B~70?LVQdjkdm3&1rgS5{#)N z_`@aVoN!1j2irlkGTyOy$DDSB zdLh3#-+rSu7NwiFO$6&Ud}Qrv3-p)x#MPX5g~n(S+LO<^XS zIp}z{-3^TPWcA`F!gSkeCM&|lydI$5bk=A35j96#A~Z@cT(6JZIvj*0NW6hGwl5j% zv9~S}cL|A1+5HlGG#xyiw#bs124aXvSprcG3ng-~1rQvp<%uedXON0s1VPh7o8cR5 z0vw}h###H{qDp;qd%Db#J7}e;G6k}v3f}1jZdM4o^B}Bm-nc7<`j^M>9d5`6^{7xj z=8^o3XE&Nc{eBnVm|A>78OYJFP0fR>EapO^L6)|Vk5=qYXPQ%2J}Z?ePAI{6l94H0 zsw6?NqC`Lr3H;Mizj^MVcV2kpojb0%;FKfwskPZ!%hh;fj%g+sWr%)WxZqB@z>M3+ z(0?DvH5&&b{{K5}ikxLH`8wLQFz145c7)Q(0)EoDOcpIO(aH?d@MIWgJ*=%9i= z7DtWB1=<}rDhikVqkVu!wI6W29tY-WUodmGmy4q19ayd9z}YNA$>|6dp#tU?qLjIM zUF}jbsGIIWM)>+Z12dNlL0l!j{5DwOsVX!m%GsFZWM-t5cvskkpvb*j6t4N>%>|dk zqG{>G5_lmn2G|c1h(5K}cBxqMa|Ga0g{WRsBNB$8;PK}G| zpL;=H!0FJ6xefE80I^|<)=vvZaml3vAf=zNs7KAO9!&urjf82oPrH5(6fvw;U`}`! z+usJZx;HNCI?&Z9B10{y1LJrmvX^aC&Sj!!OuLRz`%)>&8O%8h)5=mdWIs-duGE}` zsLO~0kLx1P#Uq=sDO+&4C4v`!&NW#ZFqDfWtUU+O+I|-u5sr#CI7fq-(BoJX1|Kk?bvf zpiP*-e;Xnk@o~}=1ZF-M4V*UXV%V@MfBf+s-C)S}{Qd4ayE;IR|K}#B0qx#eqP3v< zt*gErRDEnv@@Yy(FUb3{jDn0GaLW`R42+_h3~3mBr)65Yi5jRzN7&RCNJ1(4*mTY~ z%TAvk>^Y5|8v5dNhwjg=z22G8zqh#rUCplEj_ZY64q2@M`(({wF5BjbXe1z!gH{}a z@ETo=%P8Y8(%6lySWHA^UK8fnWqj-~ZrDClS)gBl#Us#UbAYSd{+M~LrT0(S=ew^K z;id`>o_Ok+=U#Z}mDk>Q>z(&L_~?_*2m&GSjX%X;flf6aDGCuw3)KSxEQ~-;yafUy z0tzx!A_EI~T^t2*Q`>DngN%>`rELHP493qpqw{FVIN;;y=%J)JgDPt}5hlE@ zYTq7-M>`||h!)up|7?O|%|85AiM%&Jm8$Z^RCCIkFt^QeFS>E~aOZ*)u_sBEB2}8S z88T(bRz{Av;z1#_e2_XT&w;1}M0PNmj5yM4gcuPG`eYh4+Rji@hkWE1PA;+PsbpqR zO=ZrIMx?k*Yy~7e)INpofbCRk;eLI##7UE<3=T=3F>}`JW#)`Ke8NA-ovC|s*y*Y5 zYk?vpwxXHnDe=kXdRDB(BxE+WjLi-&(v2r)94eD`kJ#T8Tr+93tXT1dlz2DZgSzB! zHMLIWM|LKEWO-&B$0+k!cH~m@I2DTIhzP64Cmwc^d2e<@g7hL)`9?j35XVAF3U`ta zSs=}h`HwU}{XEWxyvmc@%MF#Zcc({xq`fnOvsT`Hu%!a5V&(Lzv=FVOwk+D~%&CXI z5)3qq9}x_^K^*%6Q%W&}aeQFPDWw=f^|-Qbfq_zrAylh}W}0cH*~CVdnSuae$|lk z678OYm)Oos7vdZYsN1F#*!}gH&QSv4fa+O_p&3GG02819uN?;ijgg6wi018+GIs2Tx=`r=-;>YIxm_B&w1-0!l{Rk&L7KJ??2t4$_ z`7d0Vm7tzKKDV4o4p1Pn!WX40>x;XeR17t*hEg@x#xSFtbk$2=`#)yg3hLl|-Y0!F zNYRv-vK{i5vCuVi3*AF3hzkiI36u_5AP@8yU%-EzKP*TQP$wp^44+2Qw9wXYtDSPq zE8oOlqz=TJ4O9#{*z*ozKBDJSw!N z7$oq2Ob4f(O`P-o@p1`AcKhxHKJXlzX*v*>AxkZw$E?$4hUN_H<085 z>Ryolfu+s?tn?GW=snOM0nk4w4W)=ADG{aUfe=C&yztPXZ*3?tDm*GWd`||!iZBr% zh8i8V_4v(fS2o(+sKdIijGjyD3`4<2R~p`s79?2^N-~o=55#xYaT+ zDc)%)p1K=SHL96nQI@zTIvMlxD4kw;d`;Mj*PEjW`DzH1q{HBgRGvpn3}zZqFfF1e zA79C7B2QOBAR614Mfd|e&F)@HIJPi>vc2Hizh|O?D$Ul}%_{>_^ z00KrSla?Z0`h_pF21#esabwJAZwe%+B9(z$sOVQ@BvkjDwtb5obn+e8o~k9uiTxr~ zneq$DF{&aGfq~3Ag_bLrab}Nd>zS264TSVN=3E0(y()h-lx!L>MMm5xBoG``F{2lH z&oDjo7Tz)+upyxu;iv}vz5ug#el`t1Gbu_cy|n04JiH{vd?8bA#@nq^HtYAmN*aO| z$zhn^x#m=b*Sq8=!|*a}0yRTpDfFt~CE*1#`JuQ8EdxNXDJud{RiKjM{F>)nF3}Y7 z{YvGM6QmMpjU<-$LLAZQCGiX40y~9^aX^QJJem2pSQVI~IT*R0O`rr@If2pV=1&OWL)p9uzXu{)+%hNhdoI%Bm&)|xg8h;a7t_%qa1Gmg zRLnaRqG;;e_id-)wo;715(O}}?w_`f=l6Za7J{P1UnP0!V}u_jZ?iy|#Z*$>(rxD7 zWE9dLmnHQzCUlsl1na3(5h#l*fOu9(kI;D;_{Cor^6pc=qPqmaUz&s-ziM+QngXJg zqd?qhdwSQmo9$fTpfP<3ztz|lnb_ZTx7lyNHB3cGJvC{qTikY^j|hf3Y4s~famK}C z9NPYd|MnK~Zo?9}AQQzi-l*~Z(4p5g02&Q3D$WftGJn`we|PZUt2((VH}m6fRnsDW zR7v_qKY1(+BPZF83!)?3SUJ{>0a`tE*u{jCP%wLSe$<(Nf4Q>)f&oJ9BipY zS+64XEBxi(c=Ub3cAj)j#*j#2qR@fkE!AmAe(fZ$eBGmFgkN}oxg(E{7vmZuJhv~5 zWiJA-Ob;x>UXX(!4r9Mm<^5e$KAJJz;ONo$+Z_U@yf+vrZPL1-=##6+iNv+hbi(&kZA7BueTnnL z7WYEP9y1Z@MD(s^euC42{Za}=ZxPit{fa}8bQM!Om<+@?wkJGZix8Wai-e~8lFq6n zlM)$p4DlzCDI(bFG862NnbFerXDVp@CD$oA=dxTBq0gT?^TQeQu+$6EUt#zIf=dq# zC}hjBc4h|s0+m>u9Lzz|KHevQFZw^SE&eapXG0yo^ zK!YLDh?cHn)cYP;nY<~@^QXMM)~e3L6~6{cEANMqO=spUdwH0qNizv-MdA!R!620&RfR%c%T)BlFv<NB6s{khr06C^X0 zQt0EYm>_myN^v8naXhcS%0;L9mziO22y)-F8zc8GS>_$+qU;Vmqz(D!hk$F?w%S*8 zkYrfDy&b!M+THZJy021Zwb!Oj&p@XbRYaZ1q-sdT*9k&dJ%@oI>HOU9bOt>c3JnPMxbkq4nvH$-r)afMwQj2k`R8F@&BgD(8 z{O+y%(kmYml_E*y52UTA&(p`O%lq%-0*T)7=dD(bx}9{qF3B*?-IOaa7!X7VFsr!s zSr0#EwUh4s@MO4{g;^b{48m3dx$!;Zjx(VN=U(Q9*BeoDo|g^3JV98LT0C^$V&~nX zg9$G)+WqPh$|$yD3P?yyO5K;YnDw#u0xM|40bNF4P6=$hej9Xv(a|P~P8B1&c+AAI zw}apn>^V5t-KtoxYm+FCEMXI|=xG&W9x#G|WbrGmv5KC;%(Wl`y=!<#y+8RAab;?i z7TlJuQlyPCYjfMupxZZB)f7LL@cOmWZXiZTWr&NmFF2xi>iIwhuc$DIv335nw!+rO zBi7j0p&hZDPcOnf=cu2%w*f(-eCy_b$6eXVAbI@kJ<~vT&ya`$^t8M6dJM@ z9{PK&h25vY+k)z$2&YM+tKH7rs@Mtam}ui-Zl{WjGC*2SC=#lkiL}sFesgy?D?ER9gtF z;_p%)!FbL8jsM`^;x|q7U&!^S`qtceimnP7Fj*S498h$TPRUrcXzBt z8>a?QdFGIXgtXeX&E=m8;X7y2jyR=ZoHc->WC zBU-Yr9sHT9$_kPt0}5ljri;u02&%^1o#-%^Gl9E@JhLJyPSw-6h*4wP&R-_vF&lKr{90NdZiCJ50`vj)7gz3WAnzQ`nU6!`-ZUTweyyJF< z8!VJMP+KR$=tM(9jn*~_eGr;ijD`4Xd#u}D0@t)+Uu8Y zbnuP2dlvh3RO(SD*ZQ}gX{o!VZ?4T|=B~_S%=2*%D#`3^Bu#rn2p!y_kuD~KoShg8 zk;+s3^sR3wOrlmTJ3X|;BorE0bxJ}_l&I!-Z5D4;CoPFzBg7T8rkS?XB}_?G9n17g z+JUohFf|+ry4<@i?a9`q&ol%l+rimwXu?VNm~)OEd`xK*9JlWV(^Fn#Du+bLJ6;DH z1hP&0z`uF$MM$?$5Sd5hqMDwAxCvrq6cdDy=1dPU?YcCIfz%s$m1qXQVVG!I(A)<#i7x^Isgm(4ehFF zMg0*;KEyMh%_tLn(WLzL%?O(6@b&r&o{~mE8Ai#PsN-r<`ud$j4<^ns8mmx_#jam; zpq&W@k8Ubw8WU2s+eIf!-=-A}SXtVFF}}e-{LsY!*Kixc5D)GXYiRR`edV_-{}tHV zqUNZrZS?vmNY*!EX?kaaAkojlCfnqg zXfvusqs_6+>7c0@m7Cf?6qOr$>MPw2SB1M_rv4REfMrl9gtq6tM;A@=id)bnwq&r9kUONQDo>vi4(aFAR1Rr#Oi}t2%y4GfN*6Yx zs)UR9dR^|JnwreHU9K!sHK)Sn!ln&pV%8M2F=aYSn^9RuX;_Qd1wC%d?9xSZmiI0( ztZRBBP_(L}y?ouK{I-()GW~+HqP9tcNpFVx%V=(#3vIUJ_ARp*o*8K6DQ%3DsQ&Sm zSJ7cxa?w=i?YUv|Y}}amRr8l(_2(agI+Nk$$-?TCR_-~y*s=1P*a|}K9At92ZcZp4 z9jd9EtIdUT@Z&Qv!

YU5R#k70>AJal41At25{Jx$}@Ligtz3+@Bat0DTtZk6x}S zO-IqS#c_Z{zW@u($Lhc6pJ@xsouEaJSdDv)tE|Zf#IkJvuBEw2pCy|N8qqV=p}8}XwV-lzE)M#;RzCyF zn(+Z8F7IFH6x?^YyJEsDTZrn(tsVKfBh!ohum{QJs2y#N9v4KTs8!Pd8W%kOb@6){ z38zw8P!wH;gMdiih2?Z&^}plc9!I>sgFYXYn9V)OY{9J*NHAv5x%ZDlQw!=*B;L^G35!G?O`tGP(_)xfK7iOu?NJgBYNLt*$FIA=Nx)HeRocOV)~}xD-j=*{qvyuv~h2^uZdP_xo)so<`PeJz8t`ng%d3-&yI@>sYB3=;DC z%$ZZlI;Y61F{&>K$!l_zdUGx;YX~o}4%gUccH8z0pc-@zPHV)WacDP|4ZLxGZSFYj zkCU!k^GW8T&4ZnsmbVUIVAqpdg=AjKZt*s|Upmo_?Q)U_=W!+E=RbLP>Ffe~GN z+U$8B=Cz{?v>%569Llbw=3nmd%X0z826+7dmJ-G4k3Fv%<_PnKA3oC0#!3gVdP)9k z5YM;hvafz**GS*ak^B}^gwDoECnme!&`vBHyr*A*g;q9ulVpKFp1e_>n+^P;;vepv z>Hl;PuWXr5b=^Hvxf+GgnYeViz^c{b1c0F!ExO9th~2$XxG59&=~MY=@h@rb#2?>q z_!Ds-*q3Y$IUVAAKIPFf%U66VxAIsye~HeVKWT;-Khl4VnVy}2Do;d}y9A4h+;n4nn0-x4!B_MA^a zN^IB~Cu%yR6~;OGb5ND!g0RR{AFQ>c>8r*u4pIKq#S(M51?`E14O6P@fP)XV*-hQK zq0A{APPZ`xNxIQ#G}dTCTE(hX6$E=Vl5&SbUX-QK@cE+@E(Z#*!T)%Bl@09)g^m5y z88#uhI87Ai-spkl()*%fk6T!DUk1Ci5sX{#&y40-aS5#?kuUaTDM7KWjz2D-NHPpK z`0Q4jsXI5%I<>{^v6sQR5__sg`HBYX`;08zEE7ro5|?J#WyL|YPVbW(xgRPBqyZ_? zny8wZh#bUZ0l)Bav8J2&7VxA5W$Qol(It}dC9#*;3~&Cd=W*w6SvPE=Z^pPM{U!wd zER15WWse2?^~hJvql?%FzUMZnHyop|PM<7+kL_}oqCV?xRsGp=oPhZYk@PGU=kC$KZAFO7ge38rl1M+)FPa=CpBJvq<0ta8i!pq|$ zrEZF?_=>Q}g&E{JlA*Pig+&K6k~nXF098vbhzecx!8$X0bRNnr$@_A#n6TO-hQ6BY z_mCNh?4Cp<>+*=(DL-O3j(4XGigT{?5)Uk{FRm_(aX{Rq(J7aSF5rYP(~2`7LlGrr zU_At!E+SAP-HGhkiO6A{pm}>FE($ICpKT%o7of2fPB+ADuOU3*Q?H+EOs6|S-~wJ3 zP9b6jtgLB>ZqS_~lf^gC_&@USPn{C6Sf+9miY%(!)Nf+CE!1|oe6W+3j^pt*Yu7t` zN}5=qag|BI`n{HYmX-r1kKLIfq`xT93=;ErTQZXNm|e0MPU=7R8h2C9ftPq+pa7Hs z&W$drVijqA%)k-*u!>a^S~HWKKl%PBCb1g4%XRqz{k0#3Mf<{n#Qf9n%EZp6>DcttY{P8>sJzeQGg+%fW--ZI3;mYY(h!pG zhbqZ|9UIifZ(ruyu*$#Te$88at3DioO{NH3yJNOmt;}Y9^v^hZ@lNxDRUe3$Vha zSUotHYzw;_;@^DAv2`on|5$F$p+D#td66plOTr!-Pg{rot@cb^Om>^y+>>1iH~uJK z?we*HmgkPClTCcb_54hLc2chf-APbEf~s zN!R&=$Nx+%v4P8rp<^i*&YyL7cIDlrFb}Z^1B;Y_m|N8@vORAFOUGianv2PD?I`qH zUPr_7TZPlpfp7(joQ6;xpOwa6!i@8otbV$a?Xo#kgE{nk82s7~2*BLf_CmG&#nGg> zBIX9-gaze`B&e)9MB`HPD;-Cv3!YtsLXH_^C~~4x zRxpk1OQ@%MEj>R2j~Dft_cgdwjI85`p6LH*nlE%s0J~Z1$=|Tptl?AFF~sjw=Wk=; zOVKOF%zLbq58A@pCx+VWuv|=6hD>PqxKM{(!{vJXm6glTCDBTuM(NDvBmP34)tnnD5~hAr;0|gP z|4L+h1$Fz*(o|aM^l39QTrR(O#&SG4eLhIUJDYsw;od&;nx=r$+2FSf_w`!VG-Npt z-nU#2u`MTu*v|ES%i}%o$o5>Wt**jzP^fzo?^uV|Jy3z-5|wHJN%y5`Ys}BP-Y)_k zq~KjqpLJDVpLI<%;Bq(Sc&I+h>c%Wr-RnF%&wJg>qkOz)0yuQ5@rIR{<8^!qC|0Rw z5p<{Z{V_l7zWZm|=AV}n(0SaYP*`gB`t3~YE7jD364D@1p5Y4{yida0sf^3($o})Y{4A|oByedEzS9Y5gpz1A)lgLwB$2@Qs$V7U zdbOd<>9ALI=>P@qYO-3US7z%MMqAfUD+N8SFjV`FST=?0(|TMUvowqG z?3VCxA#5^7w5n1YBFH_R#{n}O8eNujPSHNYpJ9-Ichw0ES*W7Z4@0+V_buNc5>daD zTfG@5bj_!_WXLi|+)AA`SSGjG3nfY~%zNqZduRhh!gIeIX*JEG4pmBQ(z83>f~Nr<8&g|h5m zC7$yNn?1l`53t#rzg9s4TolxqZG^oZ8mL!y7-4FqquolmWvf0f%&-{09>$|-E zD?x!2UfDE6U^zGD#^t39&gxNOG>AiWZJ9@v}5 z(A62Ozo>F$Xmu{TO69ccv~5AaZd!=5)Va)%k9RpLS829YP0mu~>nKYygcPjK3bc%3 zKufFsxxN*&#Dz$K;W_&3l-W`i;n2h_8rz%tMOdH(iiOfXCgn=Z&pfHb%htp23Cs}0 zhVZU;nSv8f$T2BTjtiLY;@wahe+Y9NhV@>yL<0R3dnJ|CCk1gENL!kmIT3W-{nqs@ z$lvgucP`tncU|wQU0Yn>l5l@$4LQ|KCKsfV*4FI*Xh-ZT?EB||MZl=7{$3nVH7kVQ@&2OLy!Mn&?|1CQJ$dgFv{@>ZEz1)E z>uOhYxbYf_rh&~ef^eE)rDouH-c7l|JW1HT62jqT2t=*VN zW$XGle-cDgA&p8R^Ql6LLb8O;SPJ8WM9=$gwkP~bZJ;XO-b+hoOwHFRg8`XJoiWAf z(v?{Osage$8C0)6t2+BZZHA$^9M&6v@pCD|ZYV9+7>s2aeJP-OVoMpZrJ|&8>66l= zaN1>_8$)+vc-?xZehn|3?B?aEc(Ka*roU5|VP0qTy_o8Id=0%L-pV_{KEZ2?jT@Gl!p{Q2>y6Ap+wJM z|7_xB&s#}lbDHVJmQwHFbWb>czN4lKT@i|C2a5B%Bz8@h{7dY%Itm(`qpB@4E-cHF zFV5|03lFJ+jl<#4nkJVhn(5CLg5vsDb@i`y#vKeDynOy(_~3UJPI7?5o0ML)Y;snB z!d{J1LA<>zFG2R2Soapt z?l#nvXtoKM(w$QB_qW%dZ8fEm_`&Ls-t6{eAlw~$Vkf&E5N~IJu}xO5W@(0oKrYDM zb>a9Asha;~;)f_l&d_`+68Fo9t8UTO_zOrxt|iwrUznL~P{VFhi0=gcbX44*l?J}u zHl1^gJf1tfxP2BS&Fa&BPPArF@jHH&s;b-&XxOd#vcRtS+b>d%bb|ex)J~cmN}Kv0Zuz_E0xtiANHT?! zYP&q=zngEd({pTg`T9p}{x31JXZ`pUi}xJjI)HL6tnR9<4l?OU7S?b;TEW1uT++2b!E0raf881IHgctq*?2>2A36=2A6ti zE!^XMGcdbSqdJP=g`TpdrNskW7j&UUUYJq@a@}Yq%7c^3@FeK;V|MKN*H@ zN=j^aRm+;ypnDVbDV_F=N_|G7KLuK3|8RMo?tRpsLO;kzE?s0(k_7wY0N%{kr;v~$G&wmkm8$KVZ8E~SRes8D4Z@mW8=Ji(P#?V|V9%G5IH@d7 zB94t!tAvGf z?-;Kca(PBiz1`52oonxHaX9pOP+WI^Y9{EW6xZz^HBvXqj0dfPZvXLEX>wLh%mD^@&(lIi8eAniBJq1T(2g;RKy zZR6%XQyEtGP*;boqe8Q=I<~HU&XU5V97_^ZLbN6x{Hy@0+ph~^I}d6XXy;G=^eh23 ze4sVovbwp&wze~pjre%`n_XK{2^Eh@QX*c#c1(&s+a;Y-RVf(^x{N6wE_jg=F8#ZW zklN>HJ{RDX&DQp6dsetb1M`^XpGFnnVa{*P-_BjRXuk;lkngnv?Ms&)d_c8m|0f1X zZgxM1+sEegaXJ0$MF;XXn>gdGaQf^kf3%&UQq_NehfT+v$IczUm~pXr*?1uL_S^B! zk@(O^qG~h|@H^4^R0gv?3*k8(VqV8VZvllaFD!L(uNNHZ0NjZE;@_^HWMCR2`!uBs zJcmv(EmGosyznHd0j|vcoYGBt&i{$QeNG&p?JjVAL>A3QCvm=}lTT1x7y2*c0COIW z-y6~L?-5e^NTgLKfB*6d@IG7d{MfT)Wh7DoiFiae@X}1{6 zn`x|@OvbG@l=ooxo>FyJqrMM*kZo2dHp}F%D;292GLFz_?=k6L(`erSb}^w`mK3Iu z1?SU5N6hr!=g!~MPhfvarO|;b346kWJpss)*SD=np?XgxxE=B1N*N!dob4TcJDC_7 zOK>^j{*?$MpvA_3m`n|W$zw7}n#G3Nk5`jP4dkTSZ+8{=Tf3PgrYbPrYM|#91G32dGxz!iLP~u~(h%uqXp7KqdB_{&SPS9qh=13&wMpa#5^3wlXiebNyWH`Z z#iw~gNhu8}#0}p*jQswbt`f4&k%CqmJ)h3XJSZ+tJk|>!S;d)T>gMkGdYC0yHoHqZ zt83Pdnn-cnBRI0TpGfK>C!c}A;Mtk;y5)1b+jrGOO5!eU7@I>NyNQH*%fX2+;0wv* zJ`$;avpVu9wlTXO%sFc;+F2u1GduIgS$`6U1te1U*l0PZ4Ye&$Wh7hJUrSED&KgT_ zt!mzvCS~8g=lFW*?B?2Sp1Gqm5}+3bCnfXh5}YSL6Y=}MXGaSHX+ z@Wh)}Wi%_}AnxR9FnGLUcCE!x+p#l}7ojg3z$>zBFWEq-Dx$D8n08EcY>Dat znlV`7u!OPQ#gs`I@li}kOrDgeq$Nk^$KE=SAgSM(iuZP{l5&J^#jGJ->U08U^O0+UW?kWFW8542M#Q%T8Fdn2_wOo)JH&Hta|e}G*H z0Vc6m>&P;;({|hOwpaY5<3Ov)&S1@4hj)2=ukerUa*~( zy9d+vh&Sj9XkJNdr|lMRFmkblru}UtfbFndw#OF47h?5HNiY}Q%pay4f_B#iZAnEz*AT5v8c zRH~`8=tpVsU1`ZKT>4WmTIOIuPVmz$AL-eJl^2C&T-r}>IEzy?Y@sol7-(#EqFwtF`W|*uswZ0ph8+Hvl z8ewJh^ZR@+7`~`ohiWaj=e7Gt`K2<)iaO51mE!Pa5hxM}i-?MeOGrvd%gD;{UDdIP zJ`f0th>D3zNJ>e|$ci-dh>vqD`~z_GZw(pUfmpLs-e&7lxi_CYNG5A3G#6y|%h3SAY`>+Y?|Aq!sntnU zVEoZ0K*r^Q`H^l`;hK=~JN}4nb1G#fQr}tt`Me7x?Ra;wp?+U9QFrkmzt<_;@b|U4 zA0YK1kteh6OOZm!My}-}&junFwVX%BE7(l;^Ct@9542V?id7Zt#i~y3%zKs8ZKj-a zWbSaGnB_1W30DrYB2^LTkCzV zvASaVi*xKBm1V04$hH}k0J0yvM?>MN#{IXNY=FEdZ*iefk?jPYl;HLy2)3tW{T=oy z_r&2+vS{D*AUD{s_2nn;pSEewxeKk=p2Odj)lQMbVOnjL;CvZ*kA}ikjsA~0rnW#{ zRHIyIRMaj4PfBq65(L{*vc4Lx`cND$C5x=^ptsquwRN(;Z7a%Yn)U9M=4`6F{wegl zM0>nC315zpGeQ%2_^|coTE3 z{%h5Bm-kFew3mUo|C_t|W!}UeD+2Uk*Zq;_R}*dD5-`*!zmssZo+d-Y4y|G#yQ z<}ENr00RD#?a&{C&ZQj)k&DjmHAjhWLA^~@Gda7KueWMGo4XA;`90sjoEgYxwFkYR z7g|@C9qa>|ziAB}PZ;x%{p}f`?eCzTBJaK>uhk^AjHW?`a(LUu(gbuh@s@g->EWoF zwL>fka7448RwS-l=78?!W$(6KY$tcicyop;-|;0%(|$Ud_R`WWgR~k_HHgj^7FB#v zy*tL#Q$Y}q7MrgaA=MXjW(=i=yHw@04p(8__#RdaS%TgeoyoQL&@BzfPf_EgNFq_ z65ClTmXwoHOHsERy-tKWEv*|e`STZUsdLBUi4UwKFb@>Raz9_ecQYz2N?zyy)>2IK znyX^UKCWPjQy2|f#ZorgjV*C-H@0)&-l9jf;*TFpt5T}CldoIFo~XdK))Zl4)#vH$ zI7^kMl3F6F@XR|#olCy$}A2bf&_Si5#WLV_y6Cv0NB`nc>a{)S7-0#ci2l)? zTyPVAa_4=sQhq)yI~Iru=5yZ)OFDx#H^K=VkY*Ghh#ie*??m+>IsyTNOg*6kef$fKX7lV4J&JkRG4+clO@@eKjWgMC zxBDq|3rdH~r4LCv6X36>=`a=AoydpkZ$R@sYr`y8uVQ5V^x3qn+jrncbJs`*KkgT_ zjikaU-&4;$5}9R5R|9m(U;ASVgCc2G!7*U0NLxK|JmZv+&FJZ(fX=<^-L { + const skin = source.match(/const MonochromeSkinCSS = `([\s\S]*?)`;/)[1]; + const font = fs.readFileSync(path.join(__dirname, "fixtures/fonts/source-serif-4-latin.woff2")).toString("base64"); + const browser = await chromium.launch({executablePath: process.env.XMOJ_CHROMIUM || undefined}); + try { + const rendered = []; + for (const language of ['en', 'zh-CN']) { + const page = await browser.newPage(); + // The public legacy page declares en, while /web declares zh-CN. Source + // Serif 4 has no Chinese glyphs: compare the fonts Chromium actually uses, + // not just getComputedStyle().fontFamily, which matched before the fix. + const html = `

`; + await page.route("https://navbar-font.test/**", route => route.fulfill({contentType: "text/html; charset=utf-8", body: html})); + await page.goto("https://navbar-font.test/" + language); + await page.evaluate(() => document.fonts.ready); + const session = await page.context().newCDPSession(page); + await session.send('DOM.enable'); + await session.send('CSS.enable'); + const {root} = await session.send('DOM.getDocument'); + const fonts = {}; + for (const selector of ['.navbar-brand', '.navbar-nav .nav-link', '.dropdown-toggle span', '.dropdown-item']) { + const {nodeId} = await session.send('DOM.querySelector', {nodeId: root.nodeId, selector}); + const result = await session.send('CSS.getPlatformFontsForNode', {nodeId}); + fonts[selector] = result.fonts.map(font => ({name: font.familyName, glyphs: font.glyphCount})).sort((a, b) => a.name.localeCompare(b.name)); + assert.ok(fonts[selector].length, 'must inspect rendered glyphs: ' + selector); + } + assert.ok(fonts[".navbar-brand"].some(font => font.name === "Source Serif 4"), "the Latin webfont must be loaded to exercise Chinese fallback"); + rendered.push(fonts); + await session.detach(); + await page.close(); + } + assert.deepEqual(rendered[1], rendered[0], 'document language must not change the navbar Chinese fallback font'); + } finally { + await browser.close(); + } +}); From 66d0fbb6a29171b4a275b8ddeb270f2522e425dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 3 Oct 2026 15:27:46 +0000 Subject: [PATCH 2/5] 3.8.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cddb8886..834ca508 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.8.1", + "version": "3.8.2", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From ac3405d2980d69457f0c1c312b42eac1fe3fd5e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 3 Oct 2026 15:27:53 +0000 Subject: [PATCH 3/5] Update version info to 3.8.2 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index a456d0f0..3de67639 100644 --- a/Update.json +++ b/Update.json @@ -3828,6 +3828,17 @@ } ], "Notes": "

修复新版比赛页面和旧版界面顶部导航栏持续增加窗口缩放监听器的问题,减少长时间打开页面后的内存占用和缩放卡顿;页面导航栏被替换时会清理旧监听器。修复下载页面的卡片被插入导航栏占位元素、导致首行被顶部导航栏遮挡的问题。新版 /web 页面与旧版页面共享黑白主题字体加载,避免菜单栏使用不同字体。

" + }, + "3.8.2": { + "UpdateDate": 1791041267195, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 1039, + "Description": "fix: match Chinese navbar fonts between web and classic UI" + } + ], + "Notes": "

修复新版比赛页面与旧版页面导航栏中文字体不一致的问题。

" } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 3a5d84df..eaf0b0b1 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.8.1 +// @version 3.8.2 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From a7efe58ebf086bf80578c8dcbb41786517b97c49 Mon Sep 17 00:00:00 2001 From: boomzero Date: Sun, 4 Oct 2026 08:58:45 +0800 Subject: [PATCH 4/5] fix: anchor navbar before Bootstrap CSS loads --- XMOJ.user.js | 5 ++++- tests/navbar-styler.test.cjs | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index eaf0b0b1..ea1c87d8 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2827,7 +2827,7 @@ class NavbarStyler { this.navbar = navbar; this.addedClasses = ['fixed-top', 'container', 'ml-auto'].filter(name => !navbar.classList.contains(name)); this.restoreNavbarStyles = this.preserveStyles(navbar, [ - 'position', 'border-top-left-radius', 'border-top-right-radius', + 'position', 'top', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius', 'box-shadow', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'max-width', 'background-color', 'opacity', 'z-index' @@ -2886,6 +2886,9 @@ class NavbarStyler { try { let n = this.navbar; n.classList.add('fixed-top', 'container', 'ml-auto'); + // The CDN fallback may still be loading when the top bar starts. Without + // Bootstrap's .fixed-top rule, top:auto moves it down with the spacer. + n.style.top = '0'; if (UtilityEnabled("MonochromeUI")) { Object.assign(n.style, { position: 'fixed', diff --git a/tests/navbar-styler.test.cjs b/tests/navbar-styler.test.cjs index cea21d77..2cdb28fe 100644 --- a/tests/navbar-styler.test.cjs +++ b/tests/navbar-styler.test.cjs @@ -223,3 +223,44 @@ test('Chinese navbar glyphs use the same fonts under legacy and web document lan await browser.close(); } }); + +test('navbar reserves content space before Bootstrap CSS loads and restores its original top', {timeout: 60000}, async () => { + const start = 'let navbarStyler = null;'; + const end = '// Wrapped in an async IIFE'; + const navbarSource = source.slice(source.indexOf(start), source.indexOf(end)); + const browser = await chromium.launch({executablePath: process.env.XMOJ_CHROMIUM || undefined}); + try { + for (const width of [375, 1440]) { + const page = await browser.newPage({viewport: {width, height: 800}}); + for (const mono of [false, true]) { + // The resource cache can be empty and the CDN fallback can be slow + // or fail. No .fixed-top rule is available yet in this fixture. + await page.setContent('
Page content
'); + await page.evaluate(mono => { window.UtilityEnabled = name => name === 'MonochromeUI' ? mono : name === 'NewTopBar'; }, mono); + await page.addScriptTag({content: '{\n' + navbarSource + '\nUpdateNavbarStyler(); window.testStyler = navbarStyler;\n}'}); + const Measure = () => page.evaluate(() => ({ + navbarTop: document.querySelector('nav').getBoundingClientRect().top, + navbarBottom: document.querySelector('nav').getBoundingClientRect().bottom, + contentTop: document.getElementById('content').getBoundingClientRect().top + })); + const before = await Measure(); + assert.equal(before.navbarTop, mono ? 0 : 16, 'the navbar must anchor to the viewport without Bootstrap'); + assert.ok(before.contentTop >= before.navbarBottom, 'the navbar must not cover content while CSS is unavailable'); + // When Bootstrap finally arrives, the reserved space still works. + await page.addStyleTag({content: '.fixed-top { position: fixed; top: 0; right: 0; left: 0; }'}); + const after = await Measure(); + assert.ok(after.contentTop >= after.navbarBottom); + await page.evaluate(() => window.testStyler.destroy()); + assert.equal(await page.locator('nav').evaluate(node => node.style.top), '', 'remove the owned offset on teardown'); + } + await page.close(); + } + const page = await browser.newPage(); + await page.setContent(''); + await page.evaluate(() => { window.UtilityEnabled = () => true; }); + await page.addScriptTag({content: navbarSource + '\nUpdateNavbarStyler(); navbarStyler.destroy();'}); + assert.deepEqual(await page.locator('nav').evaluate(node => [node.style.top, node.style.getPropertyPriority('top')]), ['7px', 'important']); + } finally { + await browser.close(); + } +}); From c730563a2a1d48fa424df3853d2641023185d625 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 4 Oct 2026 00:59:16 +0000 Subject: [PATCH 5/5] Update time and description of 3.8.2 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 3de67639..8dc5b4f8 100644 --- a/Update.json +++ b/Update.json @@ -3830,7 +3830,7 @@ "Notes": "

修复新版比赛页面和旧版界面顶部导航栏持续增加窗口缩放监听器的问题,减少长时间打开页面后的内存占用和缩放卡顿;页面导航栏被替换时会清理旧监听器。修复下载页面的卡片被插入导航栏占位元素、导致首行被顶部导航栏遮挡的问题。新版 /web 页面与旧版页面共享黑白主题字体加载,避免菜单栏使用不同字体。

" }, "3.8.2": { - "UpdateDate": 1791041267195, + "UpdateDate": 1791075550636, "Prerelease": true, "UpdateContents": [ {