From 22fc6dfd5b6a264aab509100b86f7eaf5b6d0796 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 22 Mar 2026 15:12:00 -0500 Subject: [PATCH] Use transform-scale only for software embeds, responsive iframe for videos The archive.org embed page has a responsive player for videos/audio but renders software/games at fixed native pixel dimensions. Fetch the item's mediatype from the metadata API and branch accordingly. --- src/components/ArchiveOrgEmbed.tsx | 113 ++++++++++++++++++----------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/src/components/ArchiveOrgEmbed.tsx b/src/components/ArchiveOrgEmbed.tsx index 3da483d0..72ed4da6 100644 --- a/src/components/ArchiveOrgEmbed.tsx +++ b/src/components/ArchiveOrgEmbed.tsx @@ -8,27 +8,41 @@ interface ArchiveOrgEmbedProps { className?: string; } -/** Fetch the dimensions of the primary media file from the archive.org metadata API. */ -function useArchiveOrgDimensions(identifier: string) { +interface ArchiveOrgMeta { + /** Native width/height of the primary media file, if available. */ + dims: { width: number; height: number } | null; + /** The item's mediatype (e.g. "software", "movies", "audio", "texts"). */ + mediatype: string | null; +} + +/** Fetch metadata for an archive.org item: dimensions and mediatype. */ +function useArchiveOrgMeta(identifier: string) { return useQuery({ - queryKey: ['archive-org-dims', identifier], - queryFn: async ({ signal }): Promise<{ width: number; height: number } | null> => { - const res = await fetch(`https://archive.org/metadata/${identifier}/files`, { signal }); - if (!res.ok) return null; + queryKey: ['archive-org-meta', identifier], + queryFn: async ({ signal }): Promise => { + const res = await fetch(`https://archive.org/metadata/${identifier}`, { signal }); + if (!res.ok) return { dims: null, mediatype: null }; - const json: { result: { width?: string; height?: string; source?: string }[] } = await res.json(); - const files = json.result; + const json: { + metadata?: { mediatype?: string }; + files?: { width?: string; height?: string; source?: string }[]; + } = await res.json(); - // Prefer the original source file with dimensions, fall back to any file with dimensions. + const mediatype = json.metadata?.mediatype ?? null; + + // Extract dimensions from the files list. + const files = json.files ?? []; const withDims = files.filter((f) => f.width && f.height); const original = withDims.find((f) => f.source === 'original') ?? withDims[0]; - if (!original) return null; - const w = Number(original.width); - const h = Number(original.height); - if (!w || !h) return null; + let dims: { width: number; height: number } | null = null; + if (original) { + const w = Number(original.width); + const h = Number(original.height); + if (w && h) dims = { width: w, height: h }; + } - return { width: w, height: h }; + return { dims, mediatype }; }, staleTime: 1000 * 60 * 60, gcTime: 1000 * 60 * 60 * 24, @@ -45,19 +59,21 @@ function useArchiveOrgDimensions(identifier: string) { * Archive.org provides: * - Thumbnail: `https://archive.org/services/img/{identifier}` * - Embed: `https://archive.org/embed/{identifier}` - * - Metadata: `https://archive.org/metadata/{identifier}/files` + * - Metadata: `https://archive.org/metadata/{identifier}` * * The embed page renders the appropriate player for the content type * (video, audio, software emulation, book reader, etc.). * - * Unlike YouTube, the archive.org embed renders content at its native pixel - * size and doesn't scale to fill the iframe. To handle narrow viewports we - * give the iframe its native dimensions and use CSS `transform: scale()` to - * shrink it to fit the container width. + * For software/games, the archive.org embed renders content at its native + * pixel size and doesn't scale to fill the iframe. We use CSS + * `transform: scale()` to shrink these to fit narrow viewports. + * + * For videos and other media, the embed page has a responsive player, + * so we use a standard responsive iframe approach. */ export function ArchiveOrgEmbed({ identifier, className }: ArchiveOrgEmbedProps) { const [activated, setActivated] = useState(false); - const { data: dims } = useArchiveOrgDimensions(identifier); + const { data: meta } = useArchiveOrgMeta(identifier); const [containerWidth, setContainerWidth] = useState(0); // Measure the outer container width on mount/resize via ResizeObserver. @@ -69,7 +85,6 @@ export function ArchiveOrgEmbed({ identifier, className }: ArchiveOrgEmbedProps) } }); ro.observe(node); - // Capture initial width synchronously. setContainerWidth(node.getBoundingClientRect().width); observerRef.current = ro; }, []); @@ -77,17 +92,18 @@ export function ArchiveOrgEmbed({ identifier, className }: ArchiveOrgEmbedProps) const thumbnailSrc = `https://archive.org/services/img/${identifier}`; - // Native dimensions (fallback to a common 4:3 size). - const nativeW = dims?.width ?? 640; - const nativeH = dims?.height ?? 480; + // Software/games need the transform-scale approach because their embed + // renders at fixed native pixel dimensions. + const isSoftware = meta?.mediatype === 'software'; - // Scale factor: shrink when the container is narrower than the native width. + const nativeW = meta?.dims?.width ?? 640; + const nativeH = meta?.dims?.height ?? 480; + const paddingBottom = `${(nativeH / nativeW) * 100}%`; + + // Scale factor for software embeds. const scale = containerWidth > 0 ? Math.min(1, containerWidth / nativeW) : 1; const scaledH = nativeH * scale; - // Padding-bottom for the thumbnail facade (before activation). - const paddingBottom = `${(nativeH / nativeW) * 100}%`; - return (
e.stopPropagation()} > {activated ? ( -
-