@@ -317,11 +317,16 @@ const logoOverrides = Object.fromEntries(
317317)
318318
319319/**
320- * Render a brand mark — an `<img src="/logo/:slug">` when the slug is
321- * in the manifest, else a `<span>` plain-text fallback. No client-side
322- * image fallback so SSR HTML is final and there's no broken-image
323- * flicker. Width is computed from the manifest aspect ratio so the
324- * intrinsic image dimensions match the rendered box (no CLS).
320+ * Render a brand mark via CSS `mask-image` + `background: currentColor`
321+ * when the slug is in the manifest, else a `<span>` plain-text fallback.
322+ * No client-side image fallback so SSR HTML is final and there's no
323+ * broken-image flicker. Width is computed from the manifest aspect
324+ * ratio so the layout box matches the visual size (no CLS).
325+ *
326+ * Why mask instead of `<img>` + `filter`: iOS Safari has a long-standing
327+ * bug where applying `filter` to an `<img>` containing an SVG forces
328+ * rasterization at 1× device pixels, producing blur on retina/mobile.
329+ * `mask-image` recolors via the GPU and stays sharp at any DPR.
325330 */
326331function Mark ( {
327332 height,
@@ -339,24 +344,33 @@ function Mark({
339344 return < span className = "text-lg font-medium tracking-[-0.01em] text-primary" > { name } </ span >
340345 // Per-slug visual scale lives in config (not the SVG bytes) so we
341346 // don't have to reason about server-side viewBox clipping. We
342- // multiply both `<img>` dimensions so the layout box reflects the
343- // visual size — important for the marquee, where transform-based
344- // scaling would overflow into the overflow-hidden clip.
347+ // multiply both dimensions so the layout box reflects the visual
348+ // size — important for the marquee, where transform-based scaling
349+ // would overflow into the overflow-hidden clip.
345350 // lowercase to match the case-insensitive override map
346351 const scale = logoOverrides [ slug . toLowerCase ( ) ] ?. scale ?? 1
347352 const renderedHeight = height * scale
353+ const renderedWidth = Math . round ( renderedHeight * ratio )
354+ const url = `/logos/mono/${ slug } .svg`
348355 return (
349- < img
350- // "{name} logo" is more descriptive for SEO image search than just the
351- // brand name. The parent <a> still carries `aria-label={name}` for SR
352- // brevity, so we don't double-announce "logo" to assistive tech.
353- alt = { `${ name } logo` }
354- className = "block w-auto transition-[filter] duration-100"
356+ < span
357+ aria-hidden = "true"
358+ className = "block bg-current transition-colors duration-100"
355359 data-mark
356- height = { renderedHeight }
357- src = { `/logos/mono/${ slug } .svg` }
358- style = { { height : `${ renderedHeight } px` } }
359- width = { Math . round ( renderedHeight * ratio ) }
360+ role = "img"
361+ style = { {
362+ height : `${ renderedHeight } px` ,
363+ width : `${ renderedWidth } px` ,
364+ WebkitMaskImage : `url(${ url } )` ,
365+ maskImage : `url(${ url } )` ,
366+ WebkitMaskRepeat : 'no-repeat' ,
367+ maskRepeat : 'no-repeat' ,
368+ WebkitMaskPosition : 'left center' ,
369+ maskPosition : 'left center' ,
370+ WebkitMaskSize : 'contain' ,
371+ maskSize : 'contain' ,
372+ } }
373+ title = { name }
360374 />
361375 )
362376}
@@ -513,7 +527,9 @@ function Team() {
513527 alt = { `${ t . handle } avatar` }
514528 className = "block size-7 rounded-full border border-primary"
515529 height = { 28 }
516- src = { `https://github.com/${ t . github } .png?size=56` }
530+ // 84 = 28 × 3 so the avatar stays sharp on 3× DPR mobile
531+ // displays (the GitHub avatar service rounds up internally).
532+ src = { `https://github.com/${ t . github } .png?size=84` }
517533 width = { 28 }
518534 />
519535 ) ,
0 commit comments