Give the Learn cards a real frame instead of a gradient
The preview cards led with a generated gradient. It was a deliberate fallback — nothing renders a frame of a Cap embed without loading the embed, and loading nine embeds to decorate a grid is how a page becomes unusable on a phone — but for videos PIG serves itself the frame is right there in the file. The poster is named after the VIDEO's content hash, not its own: `overview.4d4581ae.mp4` -> `overview.4d4581ae.jpg`. Re-rendering a clip changes both names together, so a thumbnail cannot outlive what it claims to show. It needs no schema column and no manifest entry, because the name is derivable. `learnPoster.sh` cuts the frame with `thumbnail=90` starting four seconds in rather than taking frame 0: the first frame of a Playwright capture is often mid-paint, and a poster of a half-rendered page is worse than no poster. The resolver ASSERTS the poster rather than verifying it — @pig/core is pure and has no filesystem. That is safe in both directions: a missing poster 404s, which `<video poster>` renders exactly as it renders no poster, and which the card falls back from via onError. Claiming a poster that is absent is free; omitting one that exists would cost every card its thumbnail. Cap-hosted rows are unchanged and still get the gradient, verified by there being exactly five <img> elements on a page with nine resources. Also widens the media allowlist to jpg/webp. The filename pattern, the traversal rules and the symlink check are untouched and still cover them, because extension is the only axis that changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { formatLearnDuration } from '@pig/core';
|
||||
import { Badge, Card } from '@/components/ui';
|
||||
import { ArchiveControl } from './ArchiveControl';
|
||||
import { learnPlayback } from './model';
|
||||
import { LearnPoster } from './LearnPoster';
|
||||
import type { LearnResourceView } from './model';
|
||||
|
||||
@@ -21,6 +22,7 @@ export function LearnVideoCard({
|
||||
managing: boolean;
|
||||
onPlay: (resource: LearnResourceView) => void;
|
||||
}) {
|
||||
const playback = learnPlayback(resource);
|
||||
return (
|
||||
<Card className="group relative flex min-w-0 flex-col overflow-hidden transition-shadow hover:shadow-md">
|
||||
<button
|
||||
@@ -34,6 +36,10 @@ export function LearnVideoCard({
|
||||
seed={resource.id}
|
||||
track={resource.track}
|
||||
duration={formatLearnDuration(resource.durationSeconds)}
|
||||
// Only a PIG-hosted clip has a real frame; a Cap embed resolves to
|
||||
// an iframe and falls back to the generated poster.
|
||||
poster={playback?.kind === 'video' ? playback.poster : null}
|
||||
alt={`Still from ${resource.title}`}
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1.5 p-4">
|
||||
{/* break-words, not truncate: a title is the only way to tell two
|
||||
|
||||
Reference in New Issue
Block a user