fix(web): tighten library poster grid
This commit is contained in:
+66
-32
@@ -3627,6 +3627,11 @@ interface LibraryGroup {
|
||||
rows: HTMLUListElement;
|
||||
}
|
||||
|
||||
interface LibraryGridItem {
|
||||
root_id: number;
|
||||
item: HTMLLIElement;
|
||||
}
|
||||
|
||||
/* ---- §9.6 poster grid (#151) -------------------------------------------- */
|
||||
|
||||
type LibraryShape = "grid" | "list";
|
||||
@@ -3658,6 +3663,7 @@ function libraryMain(
|
||||
const status = must<HTMLElement>("#library-status");
|
||||
const gridBtn = must<HTMLButtonElement>("#library-view-grid");
|
||||
const listBtn = must<HTMLButtonElement>("#library-view-list");
|
||||
const libraryRows = must<HTMLElement>("#library-libraries");
|
||||
const groups: { series: LibraryGroup; movies: LibraryGroup } = {
|
||||
series: {
|
||||
section: must<HTMLElement>("#library-series"),
|
||||
@@ -3708,6 +3714,8 @@ function libraryMain(
|
||||
}
|
||||
|
||||
function clearGroups() {
|
||||
libraryRows.replaceChildren();
|
||||
libraryRows.hidden = true;
|
||||
for (const group of [groups.series, groups.movies]) {
|
||||
group.section.hidden = true;
|
||||
group.rows.replaceChildren();
|
||||
@@ -3726,6 +3734,43 @@ function libraryMain(
|
||||
}
|
||||
}
|
||||
|
||||
function renderLibraryRows(items: LibraryGridItem[]) {
|
||||
libraryRows.replaceChildren();
|
||||
libraryRows.hidden = items.length === 0;
|
||||
const rows = [
|
||||
...roots.map((root) => ({ id: root.id, label: root.audience })),
|
||||
...[
|
||||
...new Set(
|
||||
items
|
||||
.filter((item) => !roots.some((root) => root.id === item.root_id))
|
||||
.map((item) => item.root_id),
|
||||
),
|
||||
].map((id) => ({ id, label: `root ${id}` })),
|
||||
];
|
||||
for (const root of rows) {
|
||||
const titles = items.filter((item) => item.root_id === root.id);
|
||||
if (titles.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const section = document.createElement("section");
|
||||
section.className = "deck-group library-grid-group";
|
||||
const heading = document.createElement("h3");
|
||||
heading.className = "deck-label";
|
||||
heading.textContent = root.label;
|
||||
const count = document.createElement("span");
|
||||
count.className = "deck-count readout";
|
||||
count.textContent = String(titles.length);
|
||||
const header = document.createElement("header");
|
||||
header.className = "deck-head";
|
||||
header.append(heading, count);
|
||||
const rows = document.createElement("ul");
|
||||
rows.className = "deck-rows grid-rows";
|
||||
rows.append(...titles.map((item) => item.item));
|
||||
section.append(header, rows);
|
||||
libraryRows.append(section);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
const library = data;
|
||||
if (!library) {
|
||||
@@ -3754,9 +3799,21 @@ function libraryMain(
|
||||
};
|
||||
|
||||
if (shape === "grid") {
|
||||
renderGroup(groups.series, seriesShown, (series) => seriesCard(series, roots, openSeries));
|
||||
renderGroup(groups.movies, moviesShown, (movie) => movieCard(movie, roots, openMovie));
|
||||
groups.series.section.hidden = true;
|
||||
groups.movies.section.hidden = true;
|
||||
renderLibraryRows([
|
||||
...seriesShown.map((series) => ({
|
||||
root_id: series.root_id,
|
||||
item: seriesCard(series, openSeries),
|
||||
})),
|
||||
...moviesShown.map((movie) => ({
|
||||
root_id: movie.root_id,
|
||||
item: movieCard(movie, openMovie),
|
||||
})),
|
||||
]);
|
||||
} else {
|
||||
libraryRows.replaceChildren();
|
||||
libraryRows.hidden = true;
|
||||
renderGroup(groups.series, seriesShown, (series) => seriesRow(series, roots, openSeries));
|
||||
renderGroup(groups.movies, moviesShown, (movie) => libraryRow(movie, roots, openMovie));
|
||||
}
|
||||
@@ -3865,15 +3922,14 @@ function seriesRow(
|
||||
}
|
||||
|
||||
/**
|
||||
* One §9.6 grid tile (#151): poster at w342, title and year beneath, then
|
||||
* the chips the row view carries — a card is legible before it is pretty.
|
||||
* One §9.6 grid tile (#151): poster, title and operational chips. The grid
|
||||
* supplies the library context, so cards do not repeat it.
|
||||
* A missing poster becomes a same-ratio placeholder carrying its title,
|
||||
* so the grid never reflows and never has a hole in it.
|
||||
*/
|
||||
function libraryCard(
|
||||
posterPath: string | null,
|
||||
title: string,
|
||||
year: number | null,
|
||||
chips: HTMLSpanElement[],
|
||||
open: (origin: HTMLElement) => void,
|
||||
): HTMLLIElement {
|
||||
@@ -3911,15 +3967,9 @@ function libraryCard(
|
||||
art.append(img);
|
||||
}
|
||||
|
||||
const id = document.createElement("span");
|
||||
id.className = "card-id";
|
||||
const name = document.createElement("span");
|
||||
name.className = "card-name";
|
||||
name.textContent = title;
|
||||
const when = document.createElement("span");
|
||||
when.className = "card-year readout dim";
|
||||
when.textContent = year === null ? "—" : String(year);
|
||||
id.append(name, when);
|
||||
|
||||
const chipLine = document.createElement("span");
|
||||
chipLine.className = "card-chips";
|
||||
@@ -3929,7 +3979,7 @@ function libraryCard(
|
||||
|
||||
const body = document.createElement("span");
|
||||
body.className = "card-body";
|
||||
body.append(id, chipLine);
|
||||
body.append(name, chipLine);
|
||||
|
||||
card.append(art, body);
|
||||
card.addEventListener("click", () => {
|
||||
@@ -3941,11 +3991,9 @@ function libraryCard(
|
||||
|
||||
function seriesCard(
|
||||
series: LibrarySeries,
|
||||
roots: Root[],
|
||||
open: (id: number, origin: HTMLElement) => void,
|
||||
): HTMLLIElement {
|
||||
const root = roots.find((candidate) => candidate.id === series.root_id);
|
||||
const chips = [chip(root ? root.audience : `root ${series.root_id}`)];
|
||||
const chips: HTMLSpanElement[] = [];
|
||||
if (series.wanted_episodes > 0) {
|
||||
chips.push(
|
||||
countsChip(
|
||||
@@ -3966,22 +4014,14 @@ function seriesCard(
|
||||
}),
|
||||
);
|
||||
}
|
||||
const rating = ratingChip(series.vote_average);
|
||||
if (rating !== null) {
|
||||
chips.push(rating);
|
||||
}
|
||||
return libraryCard(series.poster_path, series.title, series.year, chips, (origin) =>
|
||||
open(series.id, origin),
|
||||
);
|
||||
return libraryCard(series.poster_path, series.title, chips, (origin) => open(series.id, origin));
|
||||
}
|
||||
|
||||
function movieCard(
|
||||
movie: LibraryMovie,
|
||||
roots: Root[],
|
||||
open: (movie: LibraryMovie, origin: HTMLElement) => void,
|
||||
): HTMLLIElement {
|
||||
const root = roots.find((candidate) => candidate.id === movie.root_id);
|
||||
const chips = [chip(root ? root.audience : `root ${movie.root_id}`)];
|
||||
const chips: HTMLSpanElement[] = [];
|
||||
chips.push(mediaStateChip(movie.state, movie.wanted));
|
||||
const download = downloadChip("movie", movie.id);
|
||||
if (download !== null) {
|
||||
@@ -3997,13 +4037,7 @@ function movieCard(
|
||||
if (movie.blocked) {
|
||||
chips.push(chip(NO_TARGETED_SEARCH));
|
||||
}
|
||||
const rating = ratingChip(movie.vote_average);
|
||||
if (rating !== null) {
|
||||
chips.push(rating);
|
||||
}
|
||||
return libraryCard(movie.poster_path, movie.title, movie.year, chips, (origin) =>
|
||||
open(movie, origin),
|
||||
);
|
||||
return libraryCard(movie.poster_path, movie.title, chips, (origin) => open(movie, origin));
|
||||
}
|
||||
|
||||
/* ---- tv release deck (§9.3 for season packs and single episodes) ------- */
|
||||
|
||||
+4
-17
@@ -1480,11 +1480,11 @@ body {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* auto-fill keeps every tile the same width down to a phone, where three
|
||||
narrow-but-legible columns beat two oversized ones */
|
||||
/* Wider tiles give artwork its due without forcing a desktop-style grid onto
|
||||
a phone; the slightly wider crop avoids a wall of overly tall posters. */
|
||||
.deck-rows.grid-rows {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
@@ -1517,7 +1517,7 @@ body {
|
||||
/* the poster is framed evidence, like every other image on a panel */
|
||||
.card-art {
|
||||
display: block;
|
||||
aspect-ratio: 2 / 3;
|
||||
aspect-ratio: 18 / 25;
|
||||
}
|
||||
|
||||
.card-poster {
|
||||
@@ -1556,15 +1556,7 @@ body {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
.card-id {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-1);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
flex: 1;
|
||||
/* two clamped lines: a long title stays readable where a single
|
||||
ellipsised line at tile width would not be */
|
||||
display: -webkit-box;
|
||||
@@ -1578,11 +1570,6 @@ body {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.card-year {
|
||||
flex: none;
|
||||
font-size: var(--text-2xs);
|
||||
}
|
||||
|
||||
.card-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user