Merge #163: drop cast from title detail

This commit is contained in:
Miguel Palhas
2026-08-24 12:34:34 +01:00
7 changed files with 3 additions and 206 deletions
-62
View File
@@ -12,13 +12,11 @@ import {
formatRating,
formatVoteCount,
imdbLink,
type MetadataCastMember,
movieMetadata,
resolveTrailer,
rottenTomatoesSearch,
tmdbImage,
tmdbMovieLink,
tmdbPersonLink,
tmdbSeriesLink,
tvdbLink,
updateMovie,
@@ -1288,46 +1286,6 @@ function externalLink(label: string, href: string): HTMLAnchorElement {
return link;
}
/** One top-billed cast member, linking out to their TMDB page (§9.6). */
function castItem(member: MetadataCastMember): HTMLLIElement {
const item = document.createElement("li");
item.className = "cast-item";
const link = document.createElement("a");
link.className = "cast-link";
link.href = tmdbPersonLink(member.tmdb_id);
link.target = "_blank";
link.rel = "noreferrer";
const photo = document.createElement("span");
photo.className = "cast-photo";
const image = tmdbImage(member.profile_path, "w185");
if (image !== null) {
const img = document.createElement("img");
img.src = image;
img.alt = "";
img.loading = "lazy";
// hotlinked art can 404 (§9.6); fall back to the same empty frame a
// person with no photo gets, rather than a broken-image glyph.
img.addEventListener("error", () => {
img.remove();
photo.classList.add("cast-photo-none");
});
photo.append(img);
} else {
photo.classList.add("cast-photo-none");
}
const name = document.createElement("span");
name.className = "cast-name";
name.textContent = member.name;
const character = document.createElement("span");
character.className = "cast-character readout dim";
character.textContent = member.character;
link.setAttribute("aria-label", `${member.name} as ${member.character} — on TMDB`);
link.append(photo, name, character);
link.title = `${member.name}${member.character}`;
item.append(link);
return item;
}
function movieMain(board: HTMLElement, views: HideableView[]): MovieView {
const view = must<HTMLElement>("#movie");
const deckEl = must<HTMLElement>("#deck");
@@ -1349,8 +1307,6 @@ function movieMain(board: HTMLElement, views: HideableView[]): MovieView {
const sweep = must<HTMLButtonElement>("#movie-sweep");
const remove = must<HTMLButtonElement>("#movie-remove");
const removeWrap = must<HTMLElement>("#remove-panel");
const castSection = must<HTMLElement>("#movie-cast");
const castRows = must<HTMLUListElement>("#rows-cast");
const filesSection = must<HTMLElement>("#movie-files");
const diskCount = must<HTMLElement>("#count-disk");
const diskRows = must<HTMLUListElement>("#rows-disk");
@@ -1589,8 +1545,6 @@ function movieMain(board: HTMLElement, views: HideableView[]): MovieView {
taglineEl.hidden = true;
overviewEl.hidden = true;
actionsEl.replaceChildren();
castSection.hidden = true;
castRows.replaceChildren();
}
async function loadMetadata(id: number, ticket: number) {
@@ -1660,12 +1614,6 @@ function movieMain(board: HTMLElement, views: HideableView[]): MovieView {
externalLink("rotten tomatoes", rottenTomatoesSearch(current.title, current.year)),
);
}
if (detail.cast.length > 0 && current !== null) {
castSection.hidden = false;
for (const member of detail.cast) {
castRows.append(castItem(member));
}
}
}
/* ---- on disk: ffprobe truth and honest waivers (§5.6, §5.7) ---- */
@@ -3033,8 +2981,6 @@ function seriesMain(board: HTMLElement, tvDeck: TvReleasesView, views: HideableV
const taglineEl = must<HTMLElement>("#series-tagline");
const overviewEl = must<HTMLElement>("#series-overview");
const actionsEl = must<HTMLElement>("#series-actions");
const castSection = must<HTMLElement>("#series-cast");
const castRows = must<HTMLUListElement>("#rows-series-cast");
const statusEl = must<HTMLElement>("#series-status");
const seasonsList = must<HTMLUListElement>("#rows-seasons");
@@ -3104,8 +3050,6 @@ function seriesMain(board: HTMLElement, tvDeck: TvReleasesView, views: HideableV
taglineEl.hidden = true;
overviewEl.hidden = true;
actionsEl.replaceChildren();
castSection.hidden = true;
castRows.replaceChildren();
}
async function loadMetadata(id: number, ticket: number) {
@@ -3184,12 +3128,6 @@ function seriesMain(board: HTMLElement, tvDeck: TvReleasesView, views: HideableV
externalLink("rotten tomatoes", rottenTomatoesSearch(series.title, series.year)),
);
}
if (detail.cast.length > 0) {
castSection.hidden = false;
for (const member of detail.cast) {
castRows.append(castItem(member));
}
}
}
function seasonCountsChip(season: ApiSeason): HTMLSpanElement {
-14
View File
@@ -4,15 +4,6 @@
import { type ActionOutcome, errorDetail } from "./releases";
/** One of the top-billed cast members (§9.6). */
export interface MetadataCastMember {
tmdb_id: number;
name: string;
character: string;
/** Path fragment exactly as TMDB sends it; the browser composes the URL. */
profile_path: string | null;
}
/** The one trailer worth showing, resolved by the §9.6 rule in arr-meta. */
export interface MetadataTrailer {
youtube_key: string;
@@ -35,7 +26,6 @@ export interface MovieMetadata {
vote_count: number;
homepage: string | null;
imdb_id: string | null;
cast: MetadataCastMember[];
trailer: MetadataTrailer | null;
}
@@ -129,10 +119,6 @@ export function tvdbLink(tvdbId: number): string {
return `https://www.thetvdb.com/dereferrer/series/${tvdbId}`;
}
export function tmdbPersonLink(tmdbId: number): string {
return `https://www.themoviedb.org/person/${tmdbId}`;
}
export function imdbLink(imdbId: string): string {
return `https://www.imdb.com/title/${imdbId}/`;
}
+1 -2
View File
@@ -2,7 +2,7 @@
// release schemas — same reasoning as search.ts: the generated client
// (src/api/) is uncommitted, so CI's tsc cannot see it.
import type { MetadataCastMember, MetadataTrailer } from "./movie";
import type { MetadataTrailer } from "./movie";
import type { ActionOutcome, MovieRelease, ReleasesOutcome, WaiveOutcome } from "./releases";
import { errorDetail, probedAttributeTags, waiverOverride } from "./releases";
@@ -259,7 +259,6 @@ export interface SeriesMetadata {
homepage: string | null;
/** §9.6 links out to TVDB for series; absent when the id is unknown. */
tvdb_id: number | null;
cast: MetadataCastMember[];
trailer: MetadataTrailer | null;
}
-77
View File
@@ -1681,79 +1681,6 @@ body {
flex: 1;
}
/* cast: top billed, each linking out to TMDB (§9.6) */
.cast-grid {
margin: 0;
padding: var(--space-3) var(--space-1) 0;
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: var(--space-4) var(--space-2);
}
.cast-link {
display: grid;
gap: var(--space-1);
justify-items: start;
color: inherit;
text-decoration: none;
min-width: 0;
}
.cast-link:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.cast-photo {
width: 100%;
aspect-ratio: 2 / 3;
overflow: hidden;
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--panel-raised);
transition: border-color 150ms var(--ease-out);
}
.cast-photo img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* no profile photo: dead air, not a broken frame */
.cast-photo-none::before {
content: "";
display: block;
width: 40%;
aspect-ratio: 1;
margin: 30% auto 0;
border-radius: 50%;
border: 1px solid var(--line);
}
.cast-link:hover .cast-photo,
.cast-link:focus-visible .cast-photo {
border-color: var(--accent);
}
.cast-name {
font-size: var(--text-xs);
color: var(--ink);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
}
.cast-character {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
}
/* on-disk rows share the deck's rel grammar: name leads, chips trail */
.disk-name {
min-width: 0;
@@ -1878,10 +1805,6 @@ body {
flex: 1;
}
.cast-grid {
grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
}
.board {
padding: var(--space-6) var(--space-4);
}