Notes · · 4 min
Catching a contrast failure on my own site with paintcheck
paintcheck found 12 contrast findings on the new home page: the accent blue I copied from a design reference measures 4.09:1 on white. Lighthouse also surfaced 26 prefetch 404s from a Next.js 16 static export quirk. Both fixed, both measured again.
The new hasanyahsi.com went live in a bento layout modelled on a portfolio template I liked. Before publishing I ran the two checks I tell other people to run: paintcheck (my own contrast tool) and Lighthouse. Both found something.
paintcheck: the accent colour
The reference design uses a bright blue, #4770ff, for links and buttons on white cards. It looks fine. Measured from the rendered pixels it is not fine:
! [contrast] section.hy-kart > div.hy-kart-bas > a.hy-ok
"More About Me"
16px — 4.09:1 — needs 4.5:1
rgb(73,114,255) on rgb(255,255,255)
! [contrast] div.hy-profil-dugmeler > a.hy-dugme
"Write to me"
16px — 4.16:1 — needs 4.5:1
rgb(254,254,255) on rgb(71,112,255)
Twelve findings on the home page, six on the about page, six on the contact page. All the same cause: blue text on white, or white text on that blue, at 4.09 to 4.18 to 1 against a 4.5 to 1 threshold. This is the flat-background case where CSS arithmetic is exact, so axe-core would have caught it too. I simply had not run anything yet.
Fix: the accent became #3457e8 for text and button backgrounds in the light theme (5.8:1 on white), and a lighter #6b8cff for text on the black cards in the dark theme. Visually the difference is small. After the change paintcheck reports zero contrast findings on all four pages. The remaining findings are target-size notes on the visually hidden radio inputs inside labelled chips; the label is the click target, the input is 1×1 by design.
Lighthouse: 26 requests to files that do not exist
Desktop Lighthouse on the home page: performance 96, accessibility 96, best practices 96, SEO 100. The "errors in console" audit listed ten 404s during the run, and a puppeteer script that hovers the links counted 26 on a full visit.
The requests looked like /about/__next.!KGVuKQ.about.__PAGE__.txt. Next.js 16.2 static export writes segment prefetch payloads as nested folders, out/about/__next.!KGVuKQ/about/__PAGE__.txt, while the client asks for the dotted, flat name. Every prefetch failed and every client-side navigation fell back to a full page load.
I did not find a config switch for this, so the build now runs a 30-line script that copies each nested payload to its flat name. After that: zero 404s, client navigation keeps the trailing slash, and Lighthouse desktop reads 95 / 100 / 100 / 100 with LCP 1.5 s and CLS 0.
Lighthouse mobile: the photo
The mobile run scored 76 on performance with LCP at 7.1 s under Lighthouse's simulated slow 4G. The largest element was my own photo, served as an 800×800 WebP with loading="lazy". Lazy-loading the first thing on the screen is exactly wrong; I had copied the attribute from a component that is also used lower on the page.
Fix: the profile photo is now eager with fetchpriority="high", and a 480 px variant is offered through srcset for phone widths. Measured again, the simulated mobile LCP went from 7.1 s to 6.2 s and the score from 76 to 77. That is a small move, and the breakdown says why: the image itself loads in about 120 ms; the delay is the 173 KB HTML document and the render work before it, not the picture.
So I tried the document. The logo paths were embedded twice per logo, once in the markup and once in Next's inline data payload. Moving them into one external SVG sprite took the home page from 170 KB to 82 KB. Measured again, twice, with nothing else running: 78 and 74 on mobile, LCP 5.9 s and 6.1 s. Half the document, same LCP. The lever is somewhere else, and I stopped guessing for tonight; the smaller page is still worth keeping.
The accessibility audit on mobile also flagged the menu links: below 1100 px I had hidden the label text with display: none, which removes it from the accessible name as well. It is now visually hidden instead, and the mobile accessibility score is 100.
What this note is not
None of these numbers are a certificate. Lighthouse runs on one machine with simulated throttling; paintcheck measures one viewport at one moment. The point is smaller: I copied a colour from a reference and it failed the threshold my own tool exists to check, and I only found out because I ran the tool.
If you think I measured something wrong, write to me; I will correct it and note the change here. Contact →