feat(embeds): inline YouTube/Vimeo players + media-forward video tiles
CI / Build & Quality Checks (push) Successful in 11m17s
CI / Trigger Desktop Build (push) Successful in 10s

Video link tiles (YouTube, Shorts, Vimeo) now play in place instead of only
opening a browser tab. Adds a media-forward 16:9 (9:16 for Shorts) tile with a
privacy-friendly click-to-play facade: the homeserver's cached og:image thumbnail
+ a play button, and only on click does it swap in the cookie-less
youtube-nocookie / player.vimeo iframe — so nothing loads from Google/Vimeo until
the user presses play. Gated by a new 'Inline Media Players' setting (default on);
when off it falls back to a link that opens the video in a new tab.

Also sources YouTube thumbnails from the homeserver og:image instead of
img.youtube.com, which fixes the existing broken YouTube thumbnails on the web
build (nginx img-src has no YouTube host) and removes the pre-click Google request.

Pure URL parsing + embed-URL building moved to utils/videoEmbed.ts (unit-tested).

Note: the desktop app's Tauri CSP frame-src must allow the video hosts (separate
commit in cinny-desktop).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 19:48:12 -04:00
co-authored by Claude Opus 4.8
parent 29d74eda8f
commit 93f307cd63
6 changed files with 393 additions and 210 deletions
@@ -197,6 +197,80 @@ export const MediaPlayButton = style([
},
]);
// ---------------------------------------------------------------------------
// Inline video embed (YouTube / Vimeo) — media-forward click-to-play facade
// ---------------------------------------------------------------------------
export const EmbedMediaLandscape = style([
DefaultReset,
{
position: 'relative',
width: '100%',
aspectRatio: '16 / 9',
overflow: 'hidden',
backgroundColor: color.Surface.Container,
},
]);
export const EmbedMediaPortrait = style([
DefaultReset,
{
position: 'relative',
width: toRem(220),
maxWidth: '100%',
aspectRatio: '9 / 16',
margin: '0 auto',
overflow: 'hidden',
backgroundColor: color.Surface.Container,
},
]);
// Fills the aspect box; used for both the <button> facade and the <a> fallback.
export const EmbedFacade = style([
DefaultReset,
{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
display: 'block',
padding: 0,
border: 'none',
background: 'none',
cursor: 'pointer',
':hover': {
filter: 'brightness(0.85)',
},
},
]);
export const EmbedIframe = style([
DefaultReset,
{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
border: 0,
display: 'block',
},
]);
export const EmbedPlaceholder = style([
DefaultReset,
{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#111111',
color: '#ffffff',
fontSize: toRem(28),
},
]);
// ---------------------------------------------------------------------------
// Shared square artwork thumbnail (Spotify, IMDb poster, Discord icon)
// ---------------------------------------------------------------------------