diff options
| author | Adam Soltys <[email protected]> | 2026-10-22 10:08:17 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-22 10:08:17 -0700 |
| commit | 7947be155d4cc4f43dd89d743c5e09bfac4b635b (patch) | |
| tree | 50caf83b4940167f231c98eb018b0b11d6669b35 | |
| parent | e7979230cdddde45113a7bf282f00b4192d2ab0a (diff) | |
| download | coinosv3-7947be155d4cc4f43dd89d743c5e09bfac4b635b.tar.gz coinosv3-7947be155d4cc4f43dd89d743c5e09bfac4b635b.zip | |
Videos grid, justified rows: a tile whose clip's size isn't known yet takes its still's proportions
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/features/messages.js | 18 |
1 files changed, 16 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index b209e35..34bc815 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -13926,7 +13926,18 @@ export function messagesFeature(ctx) { const clipShape = (url) => { const z = clipSize(url); return z ? (z.h > z.w ? 'tall' : z.h < z.w ? 'wide' : 'square') : null; }; // width over height, kept between a phone's upright 9:16 and a cinema 2:1 // (a stray 32:9 or 1:4 would make a tile a sliver); unknown: a square - const clipRatio = (url) => { const z = clipSize(url); return z ? Math.min(2, Math.max(0.5, z.w / z.h)) : 1; }; + // (until the clip's size is known, the still's own proportions: the + // server's and a frame grab keep the clip's shape) + const posterAr = new Map(); + const clampAr = (r) => Math.min(2, Math.max(0.5, r)); + const clipRatio = (url) => { const z = clipSize(url); return z ? clampAr(z.w / z.h) : posterAr.get(url) || 1; }; + function arFromStill(img, url) { + if (clipSize(url) || !img.naturalWidth || !img.naturalHeight) return; + const r = clampAr(img.naturalWidth / img.naturalHeight); + posterAr.set(url, r); + const tile = img.closest('.media-grid.justified .media-tile'); + if (tile) tile.style.setProperty('--ar', r.toFixed(3)); + } // true/false, or null while the clip's shape isn't known const clipFits = (url) => { if (clipsAll()) return true; const sh = clipShape(url); return sh === null ? null : sh === 'square' || sh === (screenTall() ? 'tall' : 'wide'); }; const clipsLabel = () => t(clipsAll() ? 'clipsAll' : screenTall() ? 'clipsTall' : 'clipsWide'); @@ -14236,6 +14247,7 @@ export function messagesFeature(ctx) { mediaMode === 'images' || poster ? h('img', { src: poster || tileThumbs.get(it.url) || mediaSrc(it.url), alt: '', draggable: 'false', loading: poster || tileThumbs.has(it.url) ? 'eager' : 'lazy', decoding: 'async', + onLoad: mediaMode === 'videos' ? (e) => arFromStill(e.currentTarget, it.url) : undefined, onError: (e) => { if (fromServer) { serverThumbFailed.add(it.url); render(); return; } // the frame grab's turn const b = e.target.closest('.media-tile'); if (b) b.classList.add('broken'); @@ -14271,7 +14283,9 @@ export function messagesFeature(ctx) { if (await grabFrame(url)) { if (!v.isConnected) return; const img = document.createElement('img'); - img.alt = ''; img.draggable = false; img.src = videoThumbs.get(url); + img.alt = ''; img.draggable = false; + img.addEventListener('load', () => arFromStill(img, url), { once: true }); + img.src = videoThumbs.get(url); v.replaceWith(img); if (heavy && d.size) img.after(h('span', { class: 'media-tile-badge' }, heavyLabel(url))); return; |
