Two related avatar bugs:
1. getAvatarUrl() returned empty string for S3 keys, so uploaded
avatars never displayed. Now constructs the full URL using
NEXT_PUBLIC_STORAGE_URL and NEXT_PUBLIC_AVATAR_BUCKET_NAME,
with support for both path-style (MinIO) and virtual-hosted
(Tigris/AWS S3) URLs.
2. Avatar crop scaled canvas by devicePixelRatio (4x pixels on
Retina), producing blobs that exceeded the 2MB upload limit
even for small source images. Now caps output at 512x512 and
uses quality=0.85 for toBlob().
Closes#440, closes#441
Co-authored-by: Claude <noreply@anthropic.com>