feat(design): one media card, and Home's hero and shelves as cards (E28 S3h)
- ui/MediaCard + mediaCardStyles: one frame for every video, Plex title and Home tile; the meta line pinned to the bottom edge, the length a badge on the picture, the media Play only where the click plays, the hover row at the picture's bottom-right (the owner's pick C). - Shelf density: on any strip a one-line title over one meta line; mixed Home shelves are landscape; rails turn content-visibility off (a card's reserved height had become the strip's). Home slot 232px, so the Play and the hover row no longer touch; a landscape shelf takes the small Play. - Home hero is a card (hover row, the whole menu, a touch "…"), three quarters wide and centred; channel / playlist tiles get Play, open and "…"; a show tile acts on its next episode, ⓘ opens the show. - Plex preview: a ~0.9 s mouse rest opens a passive panel with the whole title, facts and synopsis (popover `pointerOnly`); the synopsis and title tooltips are gone. - Compact list row B: the title leads, "…" at the end, toggles over the date on hover. - Media-info layout B (collections, crew, cast beside the facts sidebar; an empty column is not drawn); filtering values are chips there and on the show page; "Cast" over the cast-only strips. - Browse tiles hover like a card; prose capped by the `measure` utility.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import clsx from "clsx";
|
||||
import { Bookmark, Check, ListPlus, MoreHorizontal, Star } from "lucide-react";
|
||||
import { Reference, Sample } from "./labKit";
|
||||
import { MediaCard, MediaPlayGlyph } from "../components/ui/MediaCard";
|
||||
import {
|
||||
MEDIA_BADGE,
|
||||
MEDIA_BAR,
|
||||
MEDIA_HOVER_LAYER,
|
||||
MEDIA_PICTURE,
|
||||
mediaPictureBox,
|
||||
mediaTitleClass,
|
||||
} from "../components/ui/mediaCardStyles";
|
||||
import { iconButtonClass } from "../components/ui/buttonStyles";
|
||||
import { HOVER_YIELD } from "../components/ui/reveal";
|
||||
|
||||
// E28 S3h — the one media card, DECIDED (owner, 2026-09-26/27: anatomy C, shelf density, the preview
|
||||
// C, the Home hero A, list row B, info layout B). Drawn with the real ui/MediaCard and its classes;
|
||||
// hover a card to see its Play and its hover row. The preview panel, the hero and the info page are
|
||||
// live in the app (Plex, Home), not copied here.
|
||||
|
||||
const BAR_BUTTON = iconButtonClass({ variant: "media", size: "sm" });
|
||||
|
||||
function HoverRow({ save = false }: { save?: boolean }) {
|
||||
return (
|
||||
<div className={MEDIA_BAR}>
|
||||
<span className="flex gap-1">
|
||||
{[Check, ...(save ? [Bookmark] : []), ListPlus, MoreHorizontal].map((Icon, i) => (
|
||||
<span key={i} className={BAR_BUTTON}>
|
||||
<Icon />
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Picture({
|
||||
aspect,
|
||||
length,
|
||||
small = false,
|
||||
save = false,
|
||||
}: {
|
||||
aspect: "poster" | "wide";
|
||||
length: string;
|
||||
small?: boolean;
|
||||
save?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={mediaPictureBox(aspect)}>
|
||||
<div className={clsx(MEDIA_PICTURE, "bg-linear-to-br from-accent/45 to-card")}>
|
||||
<span className={clsx(MEDIA_BADGE, "bottom-1.5 right-1.5", HOVER_YIELD)}>{length}</span>
|
||||
<div className={MEDIA_HOVER_LAYER}>
|
||||
<MediaPlayGlyph small={small} />
|
||||
</div>
|
||||
</div>
|
||||
<HoverRow save={save} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const score = (
|
||||
<span className="inline-flex items-center gap-0.5">
|
||||
<Star className="h-3 w-3 fill-current text-rating" aria-hidden />
|
||||
7.8
|
||||
</span>
|
||||
);
|
||||
const plexMeta = (
|
||||
<div className="flex items-center gap-1.5 text-xs leading-6 text-muted">
|
||||
<span>2003</span>
|
||||
<span aria-hidden>·</span>
|
||||
{score}
|
||||
</div>
|
||||
);
|
||||
|
||||
export function BatchD() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Reference
|
||||
id="s3h-card"
|
||||
code="S3h"
|
||||
title="The media card"
|
||||
rule="One frame: the picture, the title, the meta pinned to the bottom edge. The length is a badge on the picture; the Play shows only where the click plays; the hover row docks bottom-right and the length steps aside. A grid card: two title lines and the full meta. On a shelf: one title line over one meta line, a 232px slot, and a landscape picture takes the smaller Play."
|
||||
>
|
||||
<div className="flex flex-wrap items-stretch gap-4">
|
||||
<Sample label="Grid · a film (poster)">
|
||||
<div className="grid w-44 grid-cols-1">
|
||||
<MediaCard
|
||||
dense
|
||||
picture={<Picture aspect="poster" length="2h 34m" />}
|
||||
title={
|
||||
<div className={clsx("min-h-6", mediaTitleClass(true))}>
|
||||
The Last Samurai, a title long enough to wrap
|
||||
</div>
|
||||
}
|
||||
meta={plexMeta}
|
||||
/>
|
||||
</div>
|
||||
</Sample>
|
||||
<Sample label="Grid · a video">
|
||||
<div className="grid w-72 grid-cols-1">
|
||||
<MediaCard
|
||||
picture={<Picture aspect="wide" length="18:04" save />}
|
||||
title={<div className={mediaTitleClass(false)}>A video title</div>}
|
||||
meta={
|
||||
<>
|
||||
<div className="text-sm text-muted">A channel</div>
|
||||
<div className="mt-0.5 text-xs text-muted">12K views · 3 days ago</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Sample>
|
||||
<Sample label="Shelf · 232px, landscape, the smaller Play">
|
||||
<div className="grid w-58 grid-cols-1">
|
||||
<MediaCard
|
||||
dense
|
||||
picture={<Picture aspect="wide" length="2h 34m" small />}
|
||||
title={
|
||||
<div className={clsx("min-h-6", mediaTitleClass(true, true))}>
|
||||
The Last Samurai, cut to one line on a shelf
|
||||
</div>
|
||||
}
|
||||
meta={plexMeta}
|
||||
/>
|
||||
</div>
|
||||
</Sample>
|
||||
</div>
|
||||
</Reference>
|
||||
<Reference
|
||||
id="s3h-measure"
|
||||
code="S3h"
|
||||
title="The reading measure"
|
||||
rule="Prose (a synopsis, a biography, a channel's description) runs at most ~65 characters a line: the `measure` utility, wherever its container can be wider."
|
||||
>
|
||||
<p className="measure text-sm leading-relaxed text-muted">
|
||||
Bourne is brought out of hiding once again by a reporter who is trying to unveil an
|
||||
operation, an upgrade to a programme, in a series of newspaper columns. Information from
|
||||
the reporter stirs a new set of memories, and Bourne must finally uncover his dark past.
|
||||
</p>
|
||||
</Reference>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user