Save source descriptions, add "Why it's #N", fix IGDB studio names
Descriptions
- Items keep the source's description (TMDB/TheTVDB overview, IGDB
summary, Wikipedia intro) in a new items.description column
(migration 3); editable in the item form
- Deezer/MusicBrainz track counts and album names are now display-only
hints, not saved as descriptions
Why it's #N
- The owner's note is presented as "Why it's #N" in the editor and the
public detail view, above an "About" section with the description
IGDB developers
- IGDB credits internal divisions ("Nintendo EAD", "Nintendo EPD
Production Group No. 8"). A developer whose name shares its first word
with its parent company is credited to the parent ("Nintendo");
independently named studios keep their name (Retro Studios, Naughty
Dog, Landfall Games)
- Fall back to the publisher when IGDB lists no developer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3923444b3b
commit
7320e3f707
16 files changed
+207
-44
No files matched your search
@@ -120,13 +120,15 @@ code { font: 0.9em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; bac
|
||||
|
||||
/* ---------- profile / home ---------- */
|
||||
.profile { display: flex; align-items: center; gap: 28px; padding-top: 56px; padding-bottom: 28px; }
|
||||
/* Profile pictures: the image as-is, just cropped to a circle. The flat
|
||||
accent fill only shows behind the initial when there's no picture. */
|
||||
.avatar {
|
||||
flex: none; display: grid; place-items: center; overflow: hidden; border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 45%, var(--bg)));
|
||||
color: var(--accent-text); font-weight: 700;
|
||||
background: var(--accent); color: var(--accent-text); font-weight: 700;
|
||||
}
|
||||
.avatar:has(img) { background: none; }
|
||||
.avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.avatar--lg { width: 112px; height: 112px; font-size: 44px; box-shadow: 0 0 0 4px var(--bg), 0 0 0 5px var(--border); }
|
||||
.avatar--lg { width: 112px; height: 112px; font-size: 44px; }
|
||||
.profile-name { font-size: clamp(30px, 5vw, 46px); font-weight: 780; }
|
||||
.profile-tagline { margin-top: 8px; color: var(--muted); font-size: 17px; max-width: 60ch; }
|
||||
.profile-stats { margin-top: 12px; color: var(--faint); font-size: 14px; }
|
||||
@@ -331,8 +333,10 @@ dialog[open] { animation: pop 0.18s cubic-bezier(.2,.8,.2,1); }
|
||||
.dlg-meta { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin: 4px 0 0; font-size: 14px; }
|
||||
.dlg-meta dt { color: var(--faint); }
|
||||
.dlg-meta dd { margin: 0; }
|
||||
.dlg-why h3, .dlg-about h3 { font: 700 11.5px/1.2 var(--font); text-transform: uppercase; letter-spacing: 0.08em; color: var(--faint); margin: 6px 0 8px; }
|
||||
.dlg-desc { color: var(--muted); font-size: 14.5px; line-height: 1.6; white-space: pre-line; }
|
||||
.dlg-note {
|
||||
margin: 4px 0 0; padding: 14px 16px; border-left: 3px solid var(--accent);
|
||||
margin: 0; padding: 14px 16px; border-left: 3px solid var(--accent);
|
||||
background: var(--accent-soft); border-radius: 0 var(--radius) var(--radius) 0;
|
||||
font-size: 15px; white-space: pre-line;
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ function renderResults(res) {
|
||||
h("div", { style: { minWidth: 0 } },
|
||||
h("div", { class: "result-title" }, r.title),
|
||||
sub && h("div", { class: "result-sub" }, sub),
|
||||
r.overview && h("div", { class: "result-overview" }, r.overview),
|
||||
(r.overview || r.hint) && h("div", { class: "result-overview" }, r.overview || r.hint),
|
||||
),
|
||||
action,
|
||||
);
|
||||
@@ -294,6 +294,7 @@ function fromResult(r) {
|
||||
return {
|
||||
title: r.title, subtitle: r.subtitle, year: r.year, externalUrl: r.externalUrl,
|
||||
imageUrl: r.imageUrl, provider: r.provider, providerId: r.providerId, extra: { ...(r.extra || {}) },
|
||||
description: r.overview || "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -344,7 +345,10 @@ function openItemForm({ item, result }) {
|
||||
const title = h("input", { required: true, maxlength: 500, value: src.title || "" });
|
||||
const subtitle = h("input", { maxlength: 500, value: src.subtitle || "" });
|
||||
const year = h("input", { maxlength: 20, value: src.year || "", inputmode: "numeric", style: { maxWidth: "140px" } });
|
||||
const note = h("textarea", { rows: 3, maxlength: 10000, value: item?.note || "", placeholder: "Why it's here. Shown to visitors." });
|
||||
const description = h("textarea", { rows: 4, maxlength: 10000, value: src.description || "", placeholder: "What it is, in a few sentences." });
|
||||
const note = h("textarea", { rows: 3, maxlength: 10000, value: item?.note || "", placeholder: "What earns it this spot? Shown to visitors." });
|
||||
const source = { tmdb: "TMDB", tvdb: "TheTVDB", igdb: "IGDB", wikipedia: "Wikipedia" }[src.provider];
|
||||
const rankLabel = editing ? `#${item.rank}` : `#${list.itemCount + 1}`;
|
||||
const link = h("input", { type: "url", maxlength: 2000, value: src.externalUrl || "", placeholder: "https://" });
|
||||
|
||||
// Theater production gets top billing with suggestions.
|
||||
@@ -438,7 +442,8 @@ function openItemForm({ item, result }) {
|
||||
field(cat.key === "theater" ? "Year (of this production)" : "Year", year),
|
||||
),
|
||||
extraInputs.length > 0 && h("div", { class: "row" }, extraInputs.map((x) => field(x.label, x.input))),
|
||||
field("Your note", note),
|
||||
field("Description", description, source ? `From ${source}. Edit it if you like.` : "Optional. Shown to visitors."),
|
||||
field(`Why it's ${rankLabel}`, note, "Your take on why it earns its place in the ranking."),
|
||||
field("Link", link, "Where the title links to on your page."),
|
||||
error,
|
||||
),
|
||||
@@ -461,7 +466,7 @@ function openItemForm({ item, result }) {
|
||||
if (production) newExtra.production = production.value.trim();
|
||||
for (const x of extraInputs) newExtra[x.key] = x.input.value.trim();
|
||||
const body = {
|
||||
title: title.value, subtitle: subtitle.value, year: year.value, note: note.value,
|
||||
title: title.value, subtitle: subtitle.value, year: year.value, note: note.value, description: description.value,
|
||||
externalUrl: link.value.trim(), extra: newExtra,
|
||||
imageUrl: clearImage ? "" : imageUrl.value.trim(), clearImage,
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ function profileSection() {
|
||||
h("div", { class: "row" }, field("Your name", ownerName, "Shown as the page heading."), field("Site title", siteTitle, "Shown in the header and browser tab. Leave blank to use “Your name's Canon”.")),
|
||||
field("Tagline", tagline),
|
||||
field("Entries per list on the home page", preview, "Visitors see the full list on each list's page. 0 shows only the heading."),
|
||||
h("label", { class: "check" }, notes, "Show my notes to visitors"),
|
||||
h("label", { class: "check" }, notes, "Show why each entry is ranked where it is"),
|
||||
h("div", { class: "settings-actions" }, save),
|
||||
);
|
||||
form.addEventListener("submit", async (e) => {
|
||||
|
||||
@@ -38,7 +38,12 @@ function renderDialog(list, idx) {
|
||||
sub && h("p", { class: "dlg-sub" }, sub),
|
||||
it.badge && h("span", { class: "badge" }, it.badge),
|
||||
meta.length > 0 && h("dl", { class: "dlg-meta" }, meta.map((m) => [h("dt", {}, m.label), h("dd", {}, m.value)])),
|
||||
it.note && h("blockquote", { class: "dlg-note" }, it.note),
|
||||
it.note && h("section", { class: "dlg-why" },
|
||||
h("h3", {}, `Why it's #${it.rank}`),
|
||||
h("blockquote", { class: "dlg-note" }, it.note)),
|
||||
it.description && h("section", { class: "dlg-about" },
|
||||
h("h3", {}, "About"),
|
||||
h("p", { class: "dlg-desc" }, it.description)),
|
||||
h("div", { class: "dlg-actions" },
|
||||
it.link && h("a", { class: "btn btn--sm", href: it.link, target: "_blank", rel: "noopener noreferrer" },
|
||||
icon("external"), it.source ? `View on ${it.source}` : "Learn more"),
|
||||
|
||||
Reference in new issue
Block a user