From 793a17dbdf594372945e01eb4d4190e4ef8276cf Mon Sep 17 00:00:00 2001 From: jessikitty Date: Fri, 19 Jun 2026 22:38:53 +1000 Subject: [PATCH] Route iOS/WebKit to transparent WebP instead of opaque VP9 WebM WebKit (all iOS browsers + desktop Safari) plays VP9 WebM but renders its alpha as opaque black. SUPPORTS_WEBM_ALPHA now excludes iOS/Safari so those devices fall through to the animated WebP fallback, which IS transparent. Desktop Chromium/Gecko keep the WebM path. --- public/preview.html | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/public/preview.html b/public/preview.html index fd7cbce..197753d 100644 --- a/public/preview.html +++ b/public/preview.html @@ -95,9 +95,17 @@ const $ = (s) => document.querySelector(s); const TYPE_COLORS = { red: 0xff3b5c, yellow: 0xffc23b, blue: 0x3bb6ff }; - // Same VP9-alpha capability check as the hunt. + // VP9-alpha WebM transparency works on desktop Chromium/Gecko but NOT on + // WebKit: all iOS browsers (Safari, Chrome, Firefox) and desktop Safari play + // the WebM but render its alpha as opaque black. Those devices must fall + // through to the animated WebP (which IS transparent), so exclude WebKit here. const SUPPORTS_WEBM_ALPHA = (() => { try { + const ua = navigator.userAgent; + const isIOS = /iPad|iPhone|iPod/.test(ua) || + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); // iPadOS 13+ poses as Mac + const isSafari = /AppleWebKit/.test(ua) && !/Chrome|Chromium|Edg|OPR/.test(ua); + if (isIOS || isSafari) return false; const v = document.createElement('video'); return !!v.canPlayType && v.canPlayType('video/webm; codecs="vp9"') !== ''; } catch { return false; } @@ -215,11 +223,10 @@ vid.muted = true; vid.loop = true; vid.playsInline = true; vid.autoplay = true; vid.preload = 'auto'; vid.src = webm; const tex = new THREE.VideoTexture(vid); - // VP9-alpha WebMs carry straight (non-premultiplied) alpha. Leave the - // texture in the default (linear/no-color-conversion) space — forcing - // SRGBColorSpace here makes three.js crush the alpha to black. The - // material below blends on the video's own alpha with premultipliedAlpha - // off so transparent regions stay see-through. + // Desktop Chromium/Gecko only (WebKit excluded above). RGBAFormat keeps + // the alpha channel on frame upload; no SRGB override (it can crush the + // alpha); premultipliedAlpha off so transparent regions stay see-through. + tex.format = THREE.RGBAFormat; tex.minFilter = THREE.LinearFilter; tex.magFilter = THREE.LinearFilter; tex.generateMipmaps = false; const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, side: THREE.DoubleSide, @@ -272,7 +279,7 @@ const hasWebm = !!(data.webm_path || data.webm); const hasImg = !!(data.image_path || data.image || data.webp_path || data.webp); const kind = hasWebm - ? ' · WebM' + (SUPPORTS_WEBM_ALPHA ? '' : ' (no VP9-alpha fallback)') + ? (SUPPORTS_WEBM_ALPHA ? ' · WebM' : ' · WebP (WebKit fallback)') : hasImg ? ' · image' : ' · procedural wisp'; $('#pv-hint').textContent = `${data.name}${kind}`; }