diff --git a/frontend/src/components/VideoCard.tsx b/frontend/src/components/VideoCard.tsx index 57a1551..5af3b53 100644 --- a/frontend/src/components/VideoCard.tsx +++ b/frontend/src/components/VideoCard.tsx @@ -322,33 +322,40 @@ function Thumb({ ); } -// The one-line row's column widths, in ONE place — the Tailwind classes below are generated from -// them, so the thresholds can't drift out of step with the boxes they describe (they did when both -// were written by hand). Each is measured against the worst case in the library AND in both -// languages; see the row's own comment for where each number comes from. +// The one-line row's column widths, in ONE place: the boxes below take these directly (as inline +// widths — Tailwind can't accept a runtime value), and so do the drop-out thresholds. Two +// hand-written sets would drift, and the symptom would be silent: a column appearing just before +// it actually fits. Each is measured against the worst case in the LIBRARY and in BOTH languages — +// see the row's own comment for where each number comes from. const ROW_COL = { - actions: 192, // w-48 — six buttons at 28 + five 4px gaps = 188 - title: 120, // — not a column: the floor below which a title isn't worth showing - channel: 128, // w-32 - meta: 104, // — RowMetaCells' own grid: 4rem + 1rem + 1rem + two 4px gaps - views: 64, // w-16 - published: 192, // w-48 + actions: 192, // six buttons at 28 + five 4px gaps = 188 + channel: 128, + meta: 104, // RowMetaCells' own grid: 4rem + 1rem + 1rem + two 4px gaps + views: 64, + published: 192, } as const; const ROW_GAP = 12; // gap-3 const ROW_PAD = 26; // px-3 both sides, plus a rounding allowance +const ROW_TITLE_MIN = 120; // not a column: the floor below which a title isn't worth showing -// What the row must measure before each column earns its place — cumulative from the left, so each -// one only appears once everything ahead of it AND a readable title already fit. -const ROW_FITS = (() => { - let w = ROW_PAD + ROW_COL.actions + ROW_GAP + ROW_COL.title; - const at = (px: number) => (w += ROW_GAP + px); - return { - channel: at(ROW_COL.channel), - meta: at(ROW_COL.meta), - views: at(ROW_COL.views), - published: at(ROW_COL.published), - }; -})(); +// What the row must measure before each column earns its place — cumulative, so each appears only +// once everything to its left AND a readable title already fit. +const ROW_BASE = ROW_PAD + ROW_COL.actions + ROW_GAP + ROW_TITLE_MIN; +const ROW_FITS = { + channel: ROW_BASE + ROW_GAP + ROW_COL.channel, + meta: ROW_BASE + ROW_GAP + ROW_COL.channel + ROW_GAP + ROW_COL.meta, + views: ROW_BASE + ROW_GAP + ROW_COL.channel + ROW_GAP + ROW_COL.meta + ROW_GAP + ROW_COL.views, + published: + ROW_BASE + + ROW_GAP + + ROW_COL.channel + + ROW_GAP + + ROW_COL.meta + + ROW_GAP + + ROW_COL.views + + ROW_GAP + + ROW_COL.published, +}; function VideoCard({ video, @@ -489,7 +496,9 @@ function VideoCard({
{/* Actions lead: this is where the eye already is (the title is the thing you read), so they're the shortest pointer trip from it. */} -
{actions}
+
+ {actions} +
{/* Everything else is shrink-0, so the title is the only thing that can give — which means once the fixed columns outgrow the row, it gives ALL of it and vanishes. Columns drop from the right as the row narrows, least important first: a title with no date beats a @@ -497,18 +506,30 @@ function VideoCard({ filter panel are doing. Until the first measure lands (width 0) show everything: the row is off-screen for that frame anyway, and guessing narrow would flash. */}
{title}
- {fits(ROW_FITS.channel) &&
{channelButton}
} + {fits(ROW_FITS.channel) && ( +
+ {channelButton} +
+ )} {fits(ROW_FITS.meta) && } {/* Right-aligned like the duration: it's a number, so the digits share an edge. 64px holds the widest bare form ("101.7K" at 48) in either language — dropping the word took this from 144 and handed the difference to the title. */} {fits(ROW_FITS.views) && ( -
+
{viewsCell(false)}
)} {fits(ROW_FITS.published) && ( -
{published}
+
+ {published} +
)} {/* Thumb draws this over the image; with no image it goes on the row itself. */} {pct > 0 && (