0d3f44935c
Two new AppConfig fields cover the data-saving story: - `imageProxy` (default `https://wsrv.nl`) — wsrv.nl/weserv-compatible image-resizing proxy. Empty string disables it. `proxyImageUrl(src, width)` and `useImageProxy()` rewrite URLs to WebP at quality 75; the `default=` param redirects to the origin if the proxy can't fetch upstream. The proxy base URL is parsed through `URL` and rejected unless it's `https:`, so user input from the settings field can't smuggle non-https schemes into <img src>. - `lowBandwidthMode` (default `false`) — forces autoplay off everywhere (VideoPlayer, LiveStreamPlayer), skips `useVideoThumbnail`'s background frame-grab, and (when the proxy is also disabled) gates feed images and link-preview thumbnails behind a tap-to-load placeholder. The two settings are independent. A privacy-conscious user can run with the proxy off without being forced into tap-to-load, and a metered-data user can keep the proxy on without ever seeing a placeholder. Tap-to-load only kicks in when both "I'm low-bandwidth" and "the proxy is off" are true (or the proxy errors in a gated context). Three new shared pieces: - `src/lib/proxyImageUrl.ts` — pure URL rewriter - `src/hooks/useImageProxy.ts` — memoized `(src, width) => string` - `src/components/MediaPlaceholder.tsx` — tap-to-load pill with optional blurhash background; rendered as `<div role="button">` so it nests cleanly inside InlineImage's outer lightbox button - `src/components/ProxiedImage.tsx` — `<img>` with proxy + onError fallback + optional placeholder gating Wired call sites with per-context widths: Inline post images (InlineImage, w=600, gated) Image gallery tiles (GridImage, w=600, gated) Lightbox (LightboxImage, w=1200) Profile banner (ProfileBannerImage, w=1200) Profile hover banner (ProfileHoverCard, w=400) Link preview thumb (LinkPreview, w=400, suppressed when low-bandwidth + no proxy) Sidebar media tile (ProfileRightSidebar, w=300) Avatars (via shadcn) (AvatarImage, w=96 default, w=128 hover card, w=256 profile header) Custom emojis (CustomEmojiImg, w=48) Badge thumbnails (BadgeThumbnail, w=max(size*2, 128)) Badge hero + glare mask (BadgeContent, w=256; BadgeDetailContent, w=320; EmbeddedNaddr, w=192) Music / podcast art (AudioKindContent, w=600; MusicTrackRow, w=96; MusicDetailContent, w=320 hero / 96 row; PodcastDetailContent, w=320) Settings UI lives in NetworkSettingsPage with the Low-Bandwidth Mode toggle at the top (above relay / Blossom plumbing) and the Image Proxy section between Blossom Servers and Image Uploads. The low-bandwidth description text adapts to whether the proxy is configured, so the proxy↔tap-to-load coupling is visible at the toggle. Video Tier 1: - VideoPlayer overrides `autoPlay` to false in low-bandwidth - LiveStreamPlayer no longer hardcodes `autoPlay`, reads config - useVideoThumbnail skips its background frame-grab entirely `lowBandwidthMode` is added to EncryptedSettingsSchema so it crosses devices alongside `autoplayVideos`. `imageProxy` is local-only (privacy / network choice, not a UX preference).