Avatar.tsx
| 1 | // 1×1 transparent GIF — used as img fallback so the browser never enters |
| 2 | // broken-image state while waiting for the JXL polyfill to decode the source. |
| 3 | const TRANSPARENT = |
| 4 | "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; |
| 5 | |
| 6 | export function Avatar({ |
| 7 | userId, |
| 8 | version, |
| 9 | size = 32, |
| 10 | }: { |
| 11 | userId: number | null; |
| 12 | version?: number | null; |
| 13 | size?: number; |
| 14 | }) { |
| 15 | const style = `width:${size}px;height:${size}px`; |
| 16 | if (!userId) { |
| 17 | return ( |
| 18 | <span |
| 19 | class="avatar avatar-placeholder" |
| 20 | style={`${style};font-size:${Math.round(size * 0.45)}px`} |
| 21 | > |
| 22 | ? |
| 23 | </span> |
| 24 | ); |
| 25 | } |
| 26 | const src = |
| 27 | version != null |
| 28 | ? `/avatars/${userId}.jxl?v=${version}` |
| 29 | : `/avatars/${userId}.jxl`; |
| 30 | return ( |
| 31 | <picture class="avatar" style={style}> |
| 32 | <source srcset={src} type="image/jxl" /> |
| 33 | <img |
| 34 | src={TRANSPARENT} |
| 35 | alt="" |
| 36 | width={String(size)} |
| 37 | height={String(size)} |
| 38 | class="avatar-img" |
| 39 | /> |
| 40 | </picture> |
| 41 | ); |
| 42 | } |
| 43 |