From 348fdcafc3302ea4f34e6375126673f4cc2f335f Mon Sep 17 00:00:00 2001 From: Terencio Agozzino Date: Fri, 30 Oct 2026 20:19:52 +0100 Subject: [PATCH 1/4] feat(favicon): a bold TA monogram on terracotta, as SVG The old icon was a thin Cinzel TA on cream, upscaled from a small image: the hairlines vanished at 16px and the cream blended into light tabs. favicon.svg is drawn from Cinzel Decorative Black outlines; favicon.ico (16, 32, 48) and apple-touch-icon.png are rendered from it. The unused icon-512.png and the redundant favicon-32x32.png are removed. --- public/apple-touch-icon.png | Bin 5525 -> 5984 bytes public/favicon-32x32.png | Bin 1104 -> 0 bytes public/favicon.ico | Bin 3747 -> 4465 bytes public/favicon.svg | 1 + public/icon-512.png | Bin 25960 -> 0 bytes src/layouts/Base.astro | 4 ++-- 6 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.svg delete mode 100644 public/icon-512.png diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index faa6499e72909bda7ea490a2256c9edf62773016..7b7ce1b54b6415c3340bae4ede8e46e0cc1eaf40 100644 GIT binary patch literal 5984 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 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png deleted file mode 100644 index f4439d6de8cfafbcd991be15770a313f46d18933..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1104 zcmV-W1h4yvP)6*Sb<9T8w!^{kn5zzvJuYn;2$juLG`{vT_mk9mxY6zh~g>`Q3Qq0KrrF;uO zjjwx;^at*r1T5r2g8JLcE&{FxNjt6S^2S`+C4>O?*Ll{0!BG$f7k&SQphQ5^^hNmo zPS^9Eh;%w}1}LeNj98YNXnXeFT|0_Kf1H}>c|2}EZySa*9mm^^dNCA=O`#t65Ke(1<`2;WljN@Pcyy`ukDZPaOFcFxS&0D!#z$a<}IAK~&ngB-% zs!yn}7O_&^Mn)Hp#+~_ym2x*ZIt+j+t&}&U_7@PS#uI#UX+@mM*YzTvxg!jKZQEE^ z1+kS;1b~DJO9FU$y5Knik3~D35mrizblStS>GTzb-wXi8MlJY@)Hx%G%4Ks=BQv^} zVk;WGs|&fmxBWnfOvhus`A=$FDQ+8vG%d?d9jkkoe7?{1@#H|vjQTU*(m>4g!Prq4 z)Pw#)@F@UJ@_1MRYqxnz1PlX!1NJihYeLXz@)ll)EYQdcxQk*=$XWKLX>qT|)C|C$ z7%Aa@O#!8pfNA-C?FVKcABXut@t&0EVl&obPPIkjrK!R!RVX2X+JzNO<+DM>ZD%L0Po(2=R?1a{G@M=B_~m03dnPd?*y)j-Vz6bxoFhcJ!H&tEHDc zUSn|rR8c`**@afKjx5VhsIbhb&#aUfYHZZA&!4nZ2eSoiWdv3om@AU6H7Niv&}N<; z?5Lmaeepnul@i%_&P14*ZkAi$tPre}_%vn|4|dd#Uwoym?@vbF;s&_+i2-oF$=LnH zv4-zIZ?Ef3y-{2Ck8VVpMfOPUX{o;T?)>5tM6@sCdZ`Tf>#EK(XSXk<+t|i`jz0h= Wc*EM4*tq-v0000Z00000AOMsC001@s001B$00000AOOMz007DY001yB z00000AOPhE004{#ku)WL0$WK$K~#90jZ@D{R8bWE&bjZ+=**~rqCE);jAF=AtTshr zn`%?5qFQ7{?Si(oX%P_wZTb_+R<&(o5Ed3~q5_E)i4#%jAS#%?@y&bh-W%tRGyXuL z3lH8q_k7`K;CcM@ZES_=k1*!ox^7@zq^HP!cjhM&ECOd}Tuhno~X z(Z(V*7XNcRCMYIE+FbTv@IVugT4-Wc8|$qS62ThN2q(s9KqO*23 zSJBnmkEXUZROaV@FmUNQ4qv;0v>LgF%3nU?$jw^_`VS&m2|>)8=HdBRDo>*_H;c-` zJfc5~@SF3PnfM6h`Dp3h0UgIEo<586#HY2n)H`6Muw0&YgUaOdu7W(7T*1i*4h&#w zY!usj`_Qqch$LFs&;>g^p68g2)-`=hh$|rqK{wiW@5MrYX%a+)p5rHBC5P5*!u55@ z@Vrd6)&U60&tTWlA^iIA9^Xb^;QPDR=ovbJLRWw!Tn14MN>48g)bgpBR657e;(x?K*;0l7K>7#gbT`Xnmvb@LEICo$4!#rXlO+%;Q^8J}7FRx2%l`l{BgObjH( z%C>G37i(X!T+aydwqn5F{2y#_WYE@Vmn#^-^6>NVktoqunW)5s)P?`SAvsJa2}ZDg z0r0yE(P=9w29ps7ACqnZ5Pt=UNklx=XXWaqLpxbx!}JkC zij$nNe8+WN1||j*_j1)Z-kSZ}Lh}V5^}ACnF5PB22>OjA2P!xtcM? zhZ;Ohf-#=J#1aXhDlz_Fp2(RQYMrQZMxOypQIl&_Nd_i+z2uaLK6FyIef&GI2vjU? zT2^?x5S9h)y7$4KN!d4|cy||ErOcQjG7+ZoLw%h5B`dA1Lw`w49h`U!H%_-8)cy~w zoSge2AyWfNp>`bj>*u0ygg+5216;>37a11<+_FrTa!Si_<4g;xCQU>2lTRafy$#13 zKSS%!hme(%n=uKQOa+`+6cbjx3U7V^uKs=uTuRvC5JuL|!>IashR@ZL$8h?aFX73} zN5zE6IQ#9_Xn)z;jD;V4it;hzG41tDhy??<-f|L_FFPX=lG3Ye8+pSj;Vme{(aj%% z2>~V~Vu9PpEgz1mNmEURZe2K!^9R2(wQfKC2Yf{(CR3;O>_*YX8bkVtmFw{R`|oC) z0dDFngsFYtN-KQDB_={oULHL8`LMiRlXAL*$77ynmw%R-NCR!HCf)SwL?duwF_b(~ zhsudlU`N8qh8~Cj5}i*UwEl9~MBysOL}0F-Q_F~H;yE=c^#+x$ddhTo3yPqXg4Ql- z<}5J%lF78>DaX`Y(OWlRMF_exT3p*Me&=>C+ z9nH#=uzw7U&k+xG!#`s->`(~Dzu1b7Gk==?;L>keYv(q)*q_BE3c(uV`@y@POP2dwPuH!%8{XW_aouAMlJ!ykWucvle8=Yz1aG9v)m zzw{|xYBfyn1CsYW3D7+e9Q)jhs9mra`F|BvMrF_aupgc0&Y<(cU+HgT|KJJt{mo3> z{|iHcWc$>fCR{vn5JeABQwRIzGnucwHGxK;TNZeV#RAPxv?5ecnOf#@6M1SC` zEWp)}CWYVew2C<;;wHkx%kX7+n#2)!L?` zJJW~L)Cqap@>zbLz}qgc$aQtK1|B?vaXe>0jC0d)`yvWk*6liUDTVnjR}aV(AlYc! zlMx3me+aBeL_t(|oYh);Y?Wsj|Gn?`o$s8sr>B$wrL?6KMhAgyHAU!H*TT5Ou?6F@ z0!lVU)XbO}&5Rdfj7DRKCN7JAnD~d;g~Y936bU$)L%LCJhQ-aufC8oCItQhN_T0bk zea!QG=XAYcdS7{x)1LF)-rw)Jzwi4g^pc$oe+|}#UAsK~{`$tcN1)Y41Jn_yf(Us4 z6edPUDMS_21r59lLyccIccx_{CL(sN=AFyL_-d0l6qD4q5Q3RJJ5Fq`wdnz$l0YEgjlIJ*L6HlT9G)p;^SM1~l{frzAB=i1|? zd8OGq3C8naj3>Xf9q_Bu7!gsxor{$K$clu>5TBX>n2yKg#$bK~PB=2o z!48H%MqFUjGB^RHkm~Ld?P-g>tS^y1UcpPHP*75a%E#8jxGq2jb|?pJhnsNa#8FtmAcEn1+`D=mQn4s* zUAc(%))u+1ddqW2-0DCeCl|Hf*@go@--eD0=j8s;SfYmjy9K*VMcr!5e^|H}dtZ1C z-T%D?ZQDM6t&xsKF{`Q?b01v;H<1L45shCtaSSJS??k9zidd)hoddYPVKbcEJkfH? z-}a!idKroqS0UMT8^Qd1EZO=E9QfI`QM=Kn&a8ax-!uO{v^M=6T_0aX-t-~_BLxUW zrud(NLJ3H2mza=zloG9mf2I^7m|uXr=`(Qq>Lpw}b_iA`SjiC1zO_%>je|HH?Z))` z{1{2aqEOlz0|LbUYonv}oM=r40usbN0I%0)pvE|ED03~lmgNM|+14rwVPXHj-f{}D zn>YMas3D2FW*atv%*aCI%$s}QG5DVBGXfHp<=;<6;%NTbC*Y-1AkXc2 zE)g(q#Tws*Mp)bWCv+K@-GNQ~Y5E+2@UuAZ<1tAJB~?rD!RxP}qpej68K+=39z)Th z2Qh8_0;Ci15m{m2e;uqA?q5a&zR%+bm0wto#O+QASs?SuCazuDds%;`iNrJsKYk>XM*pu=_B@tXs1rb(wz zx#n>Jd1}ue<#7AiGiYl*2s;$=n>{y;oGDW=r?y@Kd$=>&fALv)kgAT;Af;c53fYa{=M)xa}C1^<3fP7$e`xdtrG42d*=?MV^QDt z!LAiY#fmkUQaW3bg^(?sjgsnRIDhah1jCUb*M?yc@HNvjJsISj|DRJO1HX6GW4M0i zL%jcom;C}Lf62jbS-40p{rb23#xQfwyp?P5(cvZu?xD)C9FDbWJgyJ^WW}Fh`L!dR z=}9q`zt?gcA_U%f0{sNb|@hGT$QWA#sdiNf-^5+ghYH;c!A_Y%(T-z&|dUmwPT z0FY?~e>i*eGn}lyY5{AqRa|G{B}1^kOGrPIh@)`MJUsg2?Xc_s+*ATqz>&Ck`={TR zu9gY$GyQRT{){XJY$u37ZXN`rzl7wzX;aQLO| zqG>1Bw=O^L;cx!{xzlDK-f)?}r;%JDEQR_dpLjDThot7bGvPTrx zM0AmN1KBvZd&F3NExM=`R13VTnYbG&iU5SVcV`Qw1 znX&6#4GnrrV{=Qw^R@&mtu3F#q_YI^Mz_|QF)~)h%-A*W(({ft?|jG7?j}pC?r<<* zb4ua`L)q^CY%&x{7G(?o7!zZQCEZP1d%tQh*vlR{LFX4n;fs!?E#ARo7G{EXQ(-l4 zlxPK8D}G19(+Yoy$5TJr`lq9(2fXajp9FuG*KI7d$q*hfM6&_pPvAzlQ9zeciu3#- fc=X1dUbp!lZ00000AOIx-001@s001B$00000AOOMy003J8001yB z00000AOMC30012aku)WL0qjXcK~#90rBXd;6hRbz@9oc>F>->Wv@pR!qgH|#BRPK# zY%DA`Ct9Qvy95bZ&TS(E4+H5;X%Wb6DzS^_ZXr>g59Q+l2$3%+FcySwm6StFcA*i;lqmu!G9HhD5d1ukKTB-5`;TO z=N-iQY{}NbkUkkLXSFdGywH33rgD0FuM)Z5|9mG9Z-gM4tel6H$K-1*Otn8d2q&Md zbODU$%(UY;wn-F8S%ELce7UmN*>pj7He&dEuD+1Rd2anlRP(g=4TO^$^Ubjv?bWX! z;(-dey4cz5D&;$WD;&@ffI`Q+YNqNgR<%qR=Jysz#Nf8GovJCXumG1JvQ8chLzv+B zv8{@6T`W_h6424A?->@JA|j;p))oc0G!R% zw6=67iP1sC(%!d3#6 zxc~qF07*rYh6ovx-2xDQ1c^yRK~#90?N(c8TvZfZd*6HKkq0)G)}+-B3$~InVGntut&XzMZX^E4VMA{GO`nZpC&)IvO{aE(^cX1d00Rm8eEE2IK7O@80dRqjv zTpKiJ%yl_|1TS^rb}#7{8h2fnJQgdSjC@nP2pkgM?LS%wm0-=qLBOR2ri_)TkA(ntJH3Rfi zM)|w9cg4<|h|&^&B4L}{*?+EkixomjWW;7xzOO9HB42~5%EG}zlj%&m7WsvVEzx?0 zKu`EHc6r^go*!I_j+kH0Z&pR7H+|AQtoLv*VKoysne)H@NjS z7O0gu0;0qQ=o=HVQKalt3JQP)K-m_r%0H{@>R2T5+lI0-uHG@BDcXBEzq$(;Z~+tQ z8kJmPz?QC_>orGCkL4ZxOTOkK7K>OW z$uxHG^945-KLL9`qzHen+pYVew*!J0k*Ye}H!h>vdrXO3tn2LGW{9mBy+R2zXwUOxK>{K|@o038Z6p@gvpF`qxS@E! zXl11=bOM?^6MXghGn-3-pe+NxTL_t(|ob6eAY!t^C|Gt^sd!Mn2Z3t1~s*t8N>dPWf1stDClQu1>0%#9A zxVR-1ZKX&}u$>gtbh%V&2;e9QX{xwVqf!F+PHFo`B9fBUcgAsq$|E$gX(A$(q(IE! z82iq5=ewQxs#)I=YTN`NKj-+tSGVswRu~IM z$xe#ERK1W)Q|pEv=J+Uqy^c8zjB!6hatVS;hIl6ep3O?@C)K;!{;V_+j6}qg5Kwrt zf2sD1g7JH#5H}%$kTmj~AyB@ZwWJ)Zf#o9jw+4*$>?^W^}SRXwryGoB>ex-jb;f|)-Z%UGP z6Jcl7lWp6sCe!uE`gfUx;?ZWQ+GGY;XXd>dr4W8yhY77@=g%~XMxT;d=O!e}Xa7Q?7@L+KK zTF``3cY#G``{MjxDg(HwP)7kr#a`S!14OX>%Uq3@SSy~QXKwHyxvYA}s8WkL( z+p6ujPs6N%h*Hw)dd=m9*oT8dNd#Ek*<8CoX-E0vxCy9}a`dY9f3{F)!uF`3 z7C{|X5(>4LhP>z{Vua4wErRzyX&%Ftb5YlM(C4`wp&o;#C! zyI`hHO+R=sneNjVtLpk`fBm9RC?si|X2h@o^gxCQ3$x;Ya^BLOG8V5GshtYA@gq0K-H~jxsrF#8+qp5GNYyI>hRO`r zQC>_r0Tttep^%6@xc&x*2J2GU+-renBJbOd83|dVl=*-}yevPKjj9-L%W|u}*yD6KkUg(t$K0w0M~XpWo{qtE_WdHa^XB1H z;x6@Dy9xx?z;zMupI0@P%^(Q`k^u-i@u`i=KfhDcS>=GN1RsloCIWAG9F$N>z3QeFY1;U2m zINVhC^t?IVg$Q&(6>j+~PoMNL002NRdf{R+opo^jjac*g%3vg--p40QAYZfK-?na8 zdSdH_#~H9?eaZ9*hP>Y?9WBSV)^D)meI6%%)^P8_e_LEnGK8jYE?$3{J z`F?3(UvRZ?(5`_@7TD2tN=o^sIUc7bL~hGSfSQC8s})t(q>g`|pW>&!(y~Xr)JD76 z;L=lEulePO$*93FEe>&jL(5zC4;AE2HE;BP?zV(-NI<-1y@f6fAL~zLzT?&PHHRD5 z-HpMve}kX2XP!}C$-?<eeE| ztw69)5_JQ^H-N-xw`XYYj3sb6?Bd7!^dn6T^DRo30BDJ diff --git a/public/icon-512.png b/public/icon-512.png deleted file mode 100644 index f8b2efd86cd3e0068597481f69a66508e991a4fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25960 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 F{y$Rio*e)H diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 2e1228b..e24b57f 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -63,8 +63,8 @@ const jsonLd = { - - + + From 9c5aeade6588ab357cee2fa865e283eab4b86ce9 Mon Sep 17 00:00:00 2001 From: Terencio Agozzino Date: Fri, 30 Oct 2026 20:19:52 +0100 Subject: [PATCH 2/4] refactor(figures): count the certifications from the list The Azure AI & Data Fundamentals badge stands for two certifications, so the figure was written by hand as 11 next to 10 badges. It is now the sum of the list, with count: 2 on the shared badge, and cannot drift. --- src/data/profile.ts | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/data/profile.ts b/src/data/profile.ts index c737132..d0bd424 100644 --- a/src/data/profile.ts +++ b/src/data/profile.ts @@ -63,19 +63,6 @@ export const companies: Logo[] = [ { name: 'Cronos', src: cronos, height: 42 }, ]; -export const figures = [ - { - value: '15+', - label: 'Years of software development, 5+ of them in professional cloud & data roles', - }, - { value: '11', label: 'AWS, Azure & Scrum certifications' }, - { value: '900+', label: 'GitHub stars on open-source projects I built' }, - { - value: '6', - label: 'Client industries: health insurance, chemicals, logistics, engineering, sport, data', - }, -]; - export type Icon = 'compass' | 'shield' | 'chart' | 'people'; export const expertise: { icon: Icon; title: string; summary: string; points: string[] }[] = [ @@ -246,6 +233,8 @@ export interface Certification { issuer: string; year: string; badge: ImageMetadata; + /** How many certifications the badge stands for, when more than one. */ + count?: number; } export const certifications: { tier: string; items: Certification[] }[] = [ @@ -285,12 +274,30 @@ export const certifications: { tier: string; items: Certification[] }[] = [ issuer: 'Microsoft', year: '2022–2023', badge: azureAi, + count: 2, }, { name: 'Professional Scrum Master I', issuer: 'Scrum.org', year: '2022', badge: psm1 }, ], }, ]; +const certificationCount = certifications + .flatMap(({ items }) => items) + .reduce((total, { count = 1 }) => total + count, 0); + +export const figures = [ + { + value: '15+', + label: 'Years of software development, 5+ of them in professional cloud & data roles', + }, + { value: String(certificationCount), label: 'AWS, Azure & Scrum certifications' }, + { value: '900+', label: 'GitHub stars on open-source projects I built' }, + { + value: '6', + label: 'Client industries: health insurance, chemicals, logistics, engineering, sport, data', + }, +]; + export const publications = [ { venue: 'Springer Nature · 2023', From faeaf3387415d1fe77e25dc910b044976da832cb Mon Sep 17 00:00:00 2001 From: Terencio Agozzino Date: Fri, 30 Oct 2026 20:19:52 +0100 Subject: [PATCH 3/4] test: browser tests on the built site, in CI Playwright 1.64.1 against astro preview: every section renders without console errors, every image loads, internal anchors exist, external links are https, the CV and icons are served, no overflow and equal gutters at 320, 390, 768 and 1280px, the four themes from a fixed clock, the navigation's reveal, and the page without JavaScript or with reduced motion. CI runs them before Lighthouse. --- .github/workflows/ci.yml | 12 +++- .gitignore | 2 + .prettierignore | 2 + CONTRIBUTING.md | 6 +- package-lock.json | 46 ++++++++++++ package.json | 4 +- playwright.config.ts | 25 +++++++ tests/site.spec.ts | 152 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 playwright.config.ts create mode 100644 tests/site.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a44807..67f6b7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,6 @@ # Checks a pull request before it is merged: formatting, types, a full build, -# and Lighthouse against the budgets in lighthouserc.json. It deploys nothing. +# browser tests, and Lighthouse against the budgets in lighthouserc.json. It +# deploys nothing. name: CI @@ -16,7 +17,7 @@ permissions: jobs: build: - name: Format, types, build, Lighthouse + name: Format, types, build, tests, Lighthouse runs-on: ubuntu-latest steps: - uses: actions/checkout@v7.0.1 @@ -34,6 +35,13 @@ jobs: - name: Types and build run: npm run build + # What a visitor sees: every section, image and link, the layout from a + # 320px phone up, the four themes, and the page without JavaScript. + - name: Browser tests + run: | + npx playwright install --with-deps chromium + npm test + # Fails if performance, accessibility, best practices or SEO drop below # their budgets, or if the page grows past 250 KB. - name: Lighthouse diff --git a/.gitignore b/.gitignore index cfd98b8..4b052ba 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ node_modules/ .DS_Store .tern-port .lighthouseci/ +test-results/ +playwright-report/ diff --git a/.prettierignore b/.prettierignore index eea5408..d1529cc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,5 @@ dist node_modules package-lock.json .lighthouseci +test-results +playwright-report diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78b739c..c254e05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,8 @@ and hold no text of their own, except section headings. 2. Run `npm run dev` and check the page in a light and a dark theme: the theme follows the clock, so change the hour, or set `data-season` and `data-time` on `` in the browser's inspector. -3. Before pushing: `npm run format`, then `npm run build && npm run lighthouse`. +3. Before pushing: `npm run format`, then `npm run build && npm test && npm run lighthouse`. + The first time, `npx playwright install chromium` downloads the test browser. 4. Open a pull request into `develop`. CI runs the same checks; it must pass. 5. To publish, merge `develop` into `master`. `deploy.yml` builds and deploys. @@ -28,6 +29,7 @@ and hold no text of their own, except section headings. | ---------------------------------- | ------------------------------- | ------------------- | | Formatting | Prettier, no diff | `npm run format` | | Types | `astro check`, strict, 0 errors | `npm run build` | +| Browser tests | All pass | `tests/` | | Lighthouse performance | ≥ 95 | `lighthouserc.json` | | Accessibility, best practices, SEO | 100 | `lighthouserc.json` | | Layout shift (CLS) | ≤ 0.01 | `lighthouserc.json` | @@ -55,7 +57,7 @@ Dependencies update themselves; nobody has to accept a pull request. - Every Monday, Dependabot opens one grouped pull request for npm and one for GitHub Actions, against `master`, with releases at least 14 days old (`cooldown` in `.github/dependabot.yml`). -- CI runs on it: format, types, build, Lighthouse budgets. +- CI runs on it: format, types, build, browser tests, Lighthouse budgets. - If CI passes, `dependabot-merge.yml` merges it, deploys, and merges `master` back into `develop`. - If CI fails, the pull request stays open and nothing ships. That is the one diff --git a/package-lock.json b/package-lock.json index c0802aa..dd596c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "devDependencies": { "@astrojs/check": "0.9.10", "@lhci/cli": "0.15.1", + "@playwright/test": "1.64.1", "prettier": "3.9.10", "prettier-plugin-astro": "1.1.1", "typescript": "6.0.3" @@ -1605,6 +1606,22 @@ "third-party-web": "latest" } }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@prettier/parse-srcset": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@prettier/parse-srcset/-/parse-srcset-3.1.0.tgz", @@ -6426,6 +6443,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/postcss": { "version": "8.5.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", diff --git a/package.json b/package.json index 83202df..7b6827e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "check": "astro check", "format": "prettier --write .", "format:check": "prettier --check .", - "lighthouse": "lhci autorun" + "lighthouse": "lhci autorun", + "test": "playwright test" }, "dependencies": { "@fontsource-variable/inter": "5.3.0", @@ -29,6 +30,7 @@ "devDependencies": { "@astrojs/check": "0.9.10", "@lhci/cli": "0.15.1", + "@playwright/test": "1.64.1", "prettier": "3.9.10", "prettier-plugin-astro": "1.1.1", "typescript": "6.0.3" diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..70ee4c2 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +// Browser tests against the built site (dist/), served by `astro preview`: +// what a visitor sees, not how the components are written. +import { defineConfig, devices } from '@playwright/test'; + +const port = 4329; + +export default defineConfig({ + testDir: 'tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? [['github'], ['list']] : 'list', + use: { + baseURL: `http://localhost:${port}`, + // The theme follows the visitor's clock; tests that depend on it set both. + timezoneId: 'Europe/Brussels', + locale: 'en-GB', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + webServer: { + command: `npm run preview -- --port ${port} --ignore-lock`, + url: `http://localhost:${port}`, + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/tests/site.spec.ts b/tests/site.spec.ts new file mode 100644 index 0000000..2a339dd --- /dev/null +++ b/tests/site.spec.ts @@ -0,0 +1,152 @@ +// What must hold for every visitor, whatever the content: the page renders +// without errors, every image and internal link works, nothing overflows on a +// phone, the theme follows the clock, and the page reads without JavaScript. +// Content itself lives in src/data/profile.ts and is checked by its types. +import { expect, test, type Page } from '@playwright/test'; + +const sections = ['expertise', 'experience', 'certifications', 'research', 'contact']; + +/** Scrolls to the bottom, so every block is revealed and every lazy image loads. */ +async function scrollThrough(page: Page) { + await page.evaluate(async () => { + for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight / 2) { + // Instant: the page scrolls smoothly, which would never reach the end. + window.scrollTo({ top: y, behavior: 'instant' }); + await new Promise((resolve) => setTimeout(resolve, 50)); + } + }); +} + +test('renders every section without errors', async ({ page }) => { + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()); + }); + + await page.goto('/'); + await scrollThrough(page); + + await expect(page.locator('h1')).toHaveText(/Terencio\s+Agozzino/, { useInnerText: true }); + for (const id of sections) { + await expect(page.locator(`#${id} h2`)).toBeVisible(); + } + await expect(page.locator('.employer').first()).toBeVisible(); + expect(errors).toEqual([]); +}); + +test('loads every image', async ({ page }) => { + await page.goto('/'); + await scrollThrough(page); + await page.waitForLoadState('networkidle'); + + const broken = await page.evaluate(() => + [...document.images].filter((img) => !img.complete || !img.naturalWidth).map((img) => img.src), + ); + expect(broken).toEqual([]); +}); + +test('serves the CV and the icons', async ({ request }) => { + const files = { + '/terencio-agozzino-cv.pdf': 'application/pdf', + '/favicon.svg': 'image/svg+xml', + '/favicon.ico': 'image/', + '/apple-touch-icon.png': 'image/png', + }; + for (const [path, type] of Object.entries(files)) { + const response = await request.get(path); + expect(response.status(), path).toBe(200); + expect(response.headers()['content-type'], path).toContain(type); + } +}); + +test('links only to anchors that exist, and to secure sites', async ({ page }) => { + await page.goto('/'); + const hrefs = await page + .locator('a[href]') + .evaluateAll((links) => links.map((link) => link.getAttribute('href') ?? '')); + + for (const href of hrefs.filter((href) => href.startsWith('#'))) { + await expect(page.locator(href), href).toHaveCount(1); + } + const external = hrefs.filter((href) => /^[a-z]+:/i.test(href) && !href.startsWith('mailto:')); + expect(external.filter((href) => !href.startsWith('https://'))).toEqual([]); + await expect(page.locator('#contact a[href^="mailto:"]')).toHaveCount(1); +}); + +for (const width of [320, 390, 768, 1280]) { + test(`fits a ${width}px screen, centred`, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }); + await page.goto('/'); + await scrollThrough(page); + + const layout = await page.evaluate(() => { + const viewport = document.documentElement.clientWidth; + const gutters = [...document.querySelectorAll('.wrap')].map((wrap) => { + const box = wrap.getBoundingClientRect(); + return Math.round(box.left) - Math.round(viewport - box.right); + }); + return { viewport, scroll: document.documentElement.scrollWidth, gutters }; + }); + expect(layout.scroll).toBe(layout.viewport); + // Every block has the same space on its left as on its right. + expect(new Set(layout.gutters)).toEqual(new Set([0])); + }); +} + +const themes = [ + { at: '2026-10-15T10:00:00+02:00', season: 'autumn', time: 'day', bg: '#f4f0e2' }, + { at: '2026-10-15T22:00:00+02:00', season: 'autumn', time: 'evening', bg: '#1d1712' }, + { at: '2027-01-15T10:00:00+01:00', season: 'winter', time: 'day', bg: '#ece8da' }, + { at: '2027-01-15T22:00:00+01:00', season: 'winter', time: 'evening', bg: '#11172a' }, +]; + +for (const { at, season, time, bg } of themes) { + test(`picks the ${season} ${time} theme from the clock`, async ({ page }) => { + await page.clock.setFixedTime(new Date(at)); + await page.goto('/'); + + const html = page.locator('html'); + await expect(html).toHaveAttribute('data-season', season); + await expect(html).toHaveAttribute('data-time', time); + await expect(page.locator('meta[name="theme-color"]')).toHaveAttribute('content', bg); + + const [r, g, b] = [1, 3, 5].map((i) => parseInt(bg.slice(i, i + 2), 16)); + await expect(page.locator('body')).toHaveCSS('background-color', `rgb(${r}, ${g}, ${b})`); + }); +} + +test('shows the name and the button in the navigation only once scrolled past', async ({ + page, +}) => { + await page.goto('/'); + const name = page.locator('#nav .logo'); + const button = page.locator('#nav .small'); + await expect(name).toBeHidden(); + await expect(button).toBeHidden(); + + await page.locator('#experience').scrollIntoViewIfNeeded(); + await expect(name).toBeVisible(); + await expect(button).toBeVisible(); +}); + +test.describe('without JavaScript', () => { + test.use({ javaScriptEnabled: false }); + + test('shows every block and the navigation', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('#nav .logo')).toBeVisible(); + await expect(page.locator('#contact h2')).toBeVisible(); + await expect(page.locator('#contact h2')).toHaveCSS('opacity', '1'); + }); +}); + +test.describe('with reduced motion', () => { + test.use({ reducedMotion: 'reduce' }); + + test('shows every block without waiting for an animation', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('#contact h2')).toHaveCSS('opacity', '1'); + await expect(page.locator('#contact h2')).toHaveCSS('transform', 'none'); + }); +}); From b7b9c1d543cd5de1d6da68660696fba1e454152a Mon Sep 17 00:00:00 2001 From: Terencio Agozzino Date: Fri, 30 Oct 2026 20:28:22 +0100 Subject: [PATCH 4/4] fix(hero): centre the stacked hero; full rows of logos Below 920px the hero stacks, but its text stayed left-aligned above a centred photo: off-balance on tablets and narrow windows. The text is now centred, with balanced lines; on phones the credentials take their own line instead of leaving the bar at the end of one. The company logos wrap in full rows of three, then two, instead of leaving one alone. Tests check both, and fail on the previous layout. --- src/components/Companies.astro | 17 +++++++++++++ src/components/Hero.astro | 33 ++++++++++++++++++++++--- tests/site.spec.ts | 44 ++++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/components/Companies.astro b/src/components/Companies.astro index 42f3f08..f61bc88 100644 --- a/src/components/Companies.astro +++ b/src/components/Companies.astro @@ -109,4 +109,21 @@ const logos = await Promise.all( width: calc(var(--w) * 0.75); } } + + /* When the six no longer fit on one line, full rows of three, then of two: + never one logo left alone on a row. */ + @media (max-width: 640px) { + .logos { + display: grid; + grid-template-columns: repeat(3, 1fr); + justify-items: center; + gap: 22px 12px; + } + } + + @media (max-width: 340px) { + .logos { + grid-template-columns: repeat(2, 1fr); + } + } diff --git a/src/components/Hero.astro b/src/components/Hero.astro index bfbdba0..571bebb 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -15,7 +15,12 @@ import Download from './icons/Download.astro'; {person.lastName}

- {person.role} {person.credentials} + {person.role} + + {/* The space belongs to the credentials, so screen readers keep it without the bar. */} + {` ${person.credentials}`}

{person.pitch}

@@ -69,7 +74,7 @@ import Download from './icons/Download.astro'; letter-spacing: -0.3px; } - .role span { + .role .sep { color: var(--primary); } @@ -121,12 +126,25 @@ import Download from './icons/Download.astro'; 0 30px 60px -30px rgb(20 14 8 / 0.45); } + /* Stacked, the text is centred above the photo, so both share one axis. */ @media (max-width: 920px) { .hero { grid-template-columns: 1fr; gap: 48px; padding-top: 40px; padding-bottom: 72px; + text-align: center; + } + + /* Centred lines of even length, with no word left alone on the last. */ + .role, + .pitch { + margin-inline: auto; + text-wrap: balance; + } + + .actions { + justify-content: center; } h1 { @@ -147,8 +165,17 @@ import Download from './icons/Download.astro'; } } - /* Phones: the two buttons share the row, or stack at full width. */ + /* Phones: the two buttons share the row, or stack at full width. The + credentials take their own line, without a bar left at the end of one. */ @media (max-width: 480px) { + .role .sep { + display: none; + } + + .credentials { + display: block; + } + .actions .btn { flex: 1 1 auto; justify-content: center; diff --git a/tests/site.spec.ts b/tests/site.spec.ts index 2a339dd..79af276 100644 --- a/tests/site.spec.ts +++ b/tests/site.spec.ts @@ -74,7 +74,7 @@ test('links only to anchors that exist, and to secure sites', async ({ page }) = await expect(page.locator('#contact a[href^="mailto:"]')).toHaveCount(1); }); -for (const width of [320, 390, 768, 1280]) { +for (const width of [320, 390, 600, 768, 1280]) { test(`fits a ${width}px screen, centred`, async ({ page }) => { await page.setViewportSize({ width, height: 900 }); await page.goto('/'); @@ -86,11 +86,51 @@ for (const width of [320, 390, 768, 1280]) { const box = wrap.getBoundingClientRect(); return Math.round(box.left) - Math.round(viewport - box.right); }); - return { viewport, scroll: document.documentElement.scrollWidth, gutters }; + // Logos differ in height: one row is the logos whose centres are close. + const rows: { middle: number; count: number }[] = []; + for (const logo of document.querySelectorAll('.logos li')) { + const box = logo.getBoundingClientRect(); + const middle = box.top + box.height / 2; + const row = rows.find((row) => Math.abs(row.middle - middle) < 12); + if (row) row.count += 1; + else rows.push({ middle, count: 1 }); + } + return { + viewport, + scroll: document.documentElement.scrollWidth, + gutters, + logosPerRow: rows.map(({ count }) => count), + }; }); expect(layout.scroll).toBe(layout.viewport); // Every block has the same space on its left as on its right. expect(new Set(layout.gutters)).toEqual(new Set([0])); + // Full rows of logos: none left alone at the end. + expect(new Set(layout.logosPerRow).size).toBe(1); + }); +} + +for (const width of [600, 900]) { + test(`centres the stacked hero's text above the photo at ${width}px`, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }); + await page.goto('/'); + + // The centre of what is drawn, not of the box: a left-aligned line in a + // full-width box would pass otherwise. + const centres = await page.evaluate(() => { + const centre = (el: Element) => { + const range = document.createRange(); + range.selectNodeContents(el); + const box = range.getBoundingClientRect(); + return Math.round(box.left + box.width / 2); + }; + const hero = ['h1', '.role', '.pitch', '.actions', '.photo-wrap']; + return hero.map((selector) => centre(document.querySelector(`#top ${selector}`)!)); + }); + const photo = centres.at(-1)!; + for (const centre of centres) { + expect(Math.abs(centre - photo)).toBeLessThanOrEqual(2); + } }); }