diff --git a/public/app.css b/public/app.css index d39c614..ac6252d 100644 --- a/public/app.css +++ b/public/app.css @@ -86,7 +86,9 @@ main { max-width: 680px; margin: 0 auto; } /* ---------- media ---------- */ -.media { border-block: 1px solid var(--line); background: #000; } +/* Card-coloured, not black: whatever space the media does not fill should + read as part of the page rather than as a border around it. */ +.media { border-block: 1px solid var(--line); background: var(--card); } .post__head + .media, .text + .media { border-top: 1px solid var(--line); } .media__bar { @@ -135,14 +137,21 @@ main { max-width: 680px; margin: 0 auto; } place-items: center; position: relative; min-width: 0; + min-height: 0; } +/* Sized by its own proportions rather than stretched to the column: a + portrait video filling the width is mostly empty space either side of it. + The cap keeps any single item within the window. */ .item img, .item video { display: block; - width: 100%; - max-height: 78vh; + width: auto; + height: auto; + max-width: 100%; + max-height: 70vh; + max-height: 70dvh; object-fit: contain; - background: #000; + background: transparent; } .item__note { @@ -162,9 +171,31 @@ main { max-width: 680px; margin: 0 auto; } overflow: visible; } -.media[data-view="grid"] .item { aspect-ratio: 1 / 1 !important; cursor: zoom-in; } +.media[data-view="grid"] .item { + aspect-ratio: 1 / 1; + cursor: zoom-in; + overflow: hidden; +} + +/* + * Filled absolutely rather than with height: 100%. The cell's height comes + * from its aspect-ratio, which a percentage height will not resolve against, + * and `aspect-ratio: auto` means "use the image's own" rather than "ignore + * ratios" -- between them a portrait thumbnail ends up twice the height of + * its cell. + */ .media[data-view="grid"] .item img, -.media[data-view="grid"] .item video { height: 100%; max-height: none; object-fit: cover; } +.media[data-view="grid"] .item video { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + max-width: none; + max-height: none; + min-height: 0; + object-fit: cover; + aspect-ratio: auto !important; +} .media[data-view="grid"] .item__note { display: none; } /* ---------- footer ---------- */ @@ -272,3 +303,30 @@ main { max-width: 680px; margin: 0 auto; } without hiding the rest. */ background: color-mix(in srgb, var(--accent, #888) 7%, transparent); } + +/* ---------- fitting the window ---------- */ + +/* + * On a large screen a single post should be readable without scrolling to + * find it -- a video especially, which you can full-screen if you want it + * bigger. + * + * The cap is the window less the chrome around the media: header, caption and + * footer come to roughly 270-350px on the posts this serves. Deriving it + * instead of subtracting a constant is the obvious thing to try and does not + * work: the card is sized by its contents, so a percentage height inside it + * resolves to auto and clamps nothing. + * + * A thread is a column of several posts and cannot fit whatever we do, so it + * keeps the looser cap above and scrolls. + */ +@media (min-width: 700px) and (min-height: 560px) { + .post:not([data-segments]) .item img, + .post:not([data-segments]) .item video { + max-height: calc(100vh - 350px); + max-height: calc(100dvh - 350px); + /* Below that the media would be too small to be worth showing; better to + let a very long caption scroll. */ + min-height: 220px; + } +} diff --git a/src/render/post.ts b/src/render/post.ts index 438ce3f..4f9dabd 100644 --- a/src/render/post.ts +++ b/src/render/post.ts @@ -4,6 +4,11 @@ import { html, raw, type Raw } from './html.ts'; import { badge, layout, originalUrlBlock } from './layout.ts'; import { linkify } from './text.ts'; +/** + * Put the ratio on the media itself, never on the figure around it. On the + * figure, at the full width of the column, a portrait video forces a box + * taller than the window and the rest of the post gets pushed off screen. + */ function aspect(item: Media): string { return item.width && item.height ? `aspect-ratio: ${item.width} / ${item.height};` : ''; } @@ -13,11 +18,12 @@ function renderItem(item: Media, index: number): Raw { if (item.kind === 'video') { const poster = item.poster ? proxyUrlFor(item.poster) : undefined; - return html`
+ return html`
${item.hls @@ -26,10 +32,11 @@ function renderItem(item: Media, index: number): Raw {
`; } - return html`
+ return html`
${item.alt ?? ''}