← back to CelebritySignatures
Museum expansion wiring: +10 museums in P195 harvest (LACMA, The Broad, MOCA, Getty, SFMOMA, Whitney, Guggenheim, Cleveland, MFA Boston, Philadelphia; QIDs verified), union-not-overwrite artists-raw writes; popup works loader now multi-source (Met -> AIC -> Cleveland, all keyless CORS) with per-tile museum label (TK-10181)
a7c40c4a75d737da96611bda4c50bcfa4377c9f3 · 2026-08-03 11:22:16 -0700 · Steve Abrams
Files touched
M public/index.htmlM scripts/fetch-artists.mjs
Diff
commit a7c40c4a75d737da96611bda4c50bcfa4377c9f3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 3 11:22:16 2026 -0700
Museum expansion wiring: +10 museums in P195 harvest (LACMA, The Broad, MOCA, Getty, SFMOMA, Whitney, Guggenheim, Cleveland, MFA Boston, Philadelphia; QIDs verified), union-not-overwrite artists-raw writes; popup works loader now multi-source (Met -> AIC -> Cleveland, all keyless CORS) with per-tile museum label (TK-10181)
---
public/index.html | 49 ++++++++++++++++++++++++++++++++++-------------
scripts/fetch-artists.mjs | 35 ++++++++++++++++++++++++++++++---
2 files changed, 68 insertions(+), 16 deletions(-)
diff --git a/public/index.html b/public/index.html
index 290dd0d..b41bc20 100644
--- a/public/index.html
+++ b/public/index.html
@@ -221,25 +221,48 @@ function evoStripHTML(qid){
e.sigs.map(s=>`<div class="evo-cell"><img loading="lazy" src="${esc(s.url)}" alt="Signature of ${esc(e.name)}${s.year?' ('+s.year+')':''}" onerror="this.closest('.evo-cell').remove()"><div class="y">${s.year||'undated'}</div></div>`).join('')
}</div>`;
}
+// Works loader — free open-museum APIs, tried in order until we have 6:
+// The Met (Open Access) → Art Institute of Chicago (PD only) → Cleveland
+// Museum of Art. All keyless + CORS-open; each tile links to the museum page.
async function loadWorks(qid, name){
if (WORKS_CACHE[qid]) return WORKS_CACHE[qid];
- const MET='https://collectionapi.metmuseum.org/public/collection/v1';
+ const works = [];
+ const last = name.toLowerCase().split(' ').pop();
+ const MAX = 6;
+ // 1) The Met
try {
+ const MET='https://collectionapi.metmuseum.org/public/collection/v1';
const s = await (await fetch(`${MET}/search?hasImages=true&artistOrCulture=true&q=${encodeURIComponent(name)}`)).json();
- const ids = (s.objectIDs||[]).slice(0,14);
- const nl = name.toLowerCase();
- const works = [];
- for (const id of ids) {
- if (works.length >= 6) break;
+ for (const id of (s.objectIDs||[]).slice(0,14)) {
+ if (works.length >= MAX) break;
try {
const o = await (await fetch(`${MET}/objects/${id}`)).json();
if (!o.primaryImageSmall) continue;
- if (!(o.artistDisplayName||'').toLowerCase().includes(nl.split(' ').pop())) continue;
- works.push({ img:o.primaryImageSmall, title:o.title||'Untitled', date:o.objectDate||'', url:o.objectURL||('https://www.metmuseum.org/art/collection/search/'+id) });
+ if (!(o.artistDisplayName||'').toLowerCase().includes(last)) continue;
+ works.push({ img:o.primaryImageSmall, title:o.title||'Untitled', date:o.objectDate||'', url:o.objectURL||('https://www.metmuseum.org/art/collection/search/'+id), src:'The Met' });
} catch {}
}
- WORKS_CACHE[qid]=works; return works;
- } catch { return []; }
+ } catch {}
+ // 2) Art Institute of Chicago
+ if (works.length < MAX) try {
+ const j = await (await fetch(`https://api.artic.edu/api/v1/artworks/search?q=${encodeURIComponent(name)}&query[term][is_public_domain]=true&fields=id,title,image_id,artist_title,date_display&limit=12`)).json();
+ for (const a of (j.data||[])) {
+ if (works.length >= MAX) break;
+ if (!a.image_id || !(a.artist_title||'').toLowerCase().includes(last)) continue;
+ works.push({ img:`https://www.artic.edu/iiif/2/${a.image_id}/full/400,/0/default.jpg`, title:a.title||'Untitled', date:a.date_display||'', url:`https://www.artic.edu/artworks/${a.id}`, src:'Art Institute of Chicago' });
+ }
+ } catch {}
+ // 3) Cleveland Museum of Art
+ if (works.length < MAX) try {
+ const j = await (await fetch(`https://openaccess-api.clevelandart.org/api/artworks/?artists=${encodeURIComponent(name)}&has_image=1&limit=10`)).json();
+ for (const a of (j.data||[])) {
+ if (works.length >= MAX) break;
+ const img = a.images?.web?.url; if (!img) continue;
+ if (!(a.creators||[]).some(c=>(c.description||'').toLowerCase().includes(last))) continue;
+ works.push({ img, title:a.title||'Untitled', date:a.creation_date||'', url:a.url||'https://www.clevelandart.org/art/collection/search', src:'Cleveland Museum of Art' });
+ }
+ } catch {}
+ WORKS_CACHE[qid]=works; return works;
}
function openArtist(qid, push=true){
const r = BY_QID[qid] || DATA.find(x=>(x.wikidata||'').endsWith('/'+qid));
@@ -255,15 +278,15 @@ function openArtist(qid, push=true){
<div class="ap-sec">License & sources</div>
<div class="ap-sub">${esc(r.image_license||'')} · <a href="${esc(r.wikidata)}" target="_blank" rel="noopener noreferrer">Wikidata</a> · <a href="${esc(r.backup_source)}" target="_blank" rel="noopener noreferrer">signature source</a></div>
${evoStripHTML(qid)}
- <div class="ap-sec">Works — The Met Open Access</div>
+ <div class="ap-sec">Works — open museum collections</div>
<div class="works" id="apWorks"><span class="ap-sub">loading works…</span></div>`;
$('#artistPop').hidden = false;
document.body.style.overflow = 'hidden';
if (push) { const u=new URLSearchParams(location.search); u.set('artist',qid); history.pushState({},'', '?'+u.toString()); }
loadWorks(qid, r.full_name).then(ws=>{
const el=$('#apWorks'); if(!el) return;
- el.innerHTML = ws.length ? ws.map(w=>`<a href="${esc(w.url)}" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="${esc(w.img)}" alt="${esc(w.title)}"><span class="wt">${esc(w.title)}${w.date?' · '+esc(w.date):''}</span></a>`).join('')
- : `<span class="ap-sub">no openly-licensed works found in the Met's collection — <a href="${esc(r.wikidata)}" target="_blank" rel="noopener noreferrer">see Wikidata</a></span>`;
+ el.innerHTML = ws.length ? ws.map(w=>`<a href="${esc(w.url)}" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="${esc(w.img)}" alt="${esc(w.title)}"><span class="wt">${esc(w.title)}${w.date?' · '+esc(w.date):''}${w.src?' · '+esc(w.src):''}</span></a>`).join('')
+ : `<span class="ap-sub">no openly-licensed works found in the open museum APIs — <a href="${esc(r.wikidata)}" target="_blank" rel="noopener noreferrer">see Wikidata</a></span>`;
});
}
function closeArtist(push=true){
diff --git a/scripts/fetch-artists.mjs b/scripts/fetch-artists.mjs
index fac3311..fee9e35 100644
--- a/scripts/fetch-artists.mjs
+++ b/scripts/fetch-artists.mjs
@@ -9,7 +9,7 @@
// the catalog's existing deceased-only rule.
// "Famous first" = ORDER BY DESC(sitelinks) — Wikipedia language-edition count.
-import { writeFile } from 'node:fs/promises';
+import { writeFile, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -33,6 +33,19 @@ const MUSEUMS = {
Q190804: 'Rijksmuseum',
Q51252: 'Uffizi Gallery',
Q160112: 'Museo del Prado',
+ // West-coast + open-collection expansion (Steve 2026-08-03: "all public
+ // museums — the Broad, MOCA, LACMA… keep building"). QIDs verified via
+ // wbsearchentities same day.
+ Q1641836: 'Los Angeles County Museum of Art',
+ Q17060100: 'The Broad',
+ Q1134646: 'Museum of Contemporary Art, Los Angeles',
+ Q731126: 'J. Paul Getty Museum',
+ Q913672: 'San Francisco Museum of Modern Art',
+ Q639791: 'Whitney Museum of American Art',
+ Q201469: 'Solomon R. Guggenheim Museum',
+ Q657415: 'Cleveland Museum of Art',
+ Q49133: 'Museum of Fine Arts Boston',
+ Q510324: 'Philadelphia Museum of Art',
};
const VALUES = Object.keys(MUSEUMS).map(q => `wd:${q}`).join(' ');
@@ -96,5 +109,21 @@ console.log('top 15 by fame:');
artists.slice(0, 15).forEach((a, i) =>
console.log(` ${i + 1}. ${a.full_name} — ${a.sitelinks} wikis, d.${a.death_year}, ${a.museums.length} museums`));
-await writeFile(join(ROOT, 'data', 'artists-raw.json'), JSON.stringify(artists, null, 2));
-console.log(`\nwrote data/artists-raw.json (${artists.length} artists)`);
+// UNION into the existing raw set (never overwrite — the roster gap-fill
+// harvester also feeds artists-raw.json). Idempotent by QID; museum
+// provenance merges; fresher sitelink/occupation data wins.
+const rawPath = join(ROOT, 'data', 'artists-raw.json');
+let existing = [];
+try { existing = JSON.parse(await readFile(rawPath, 'utf8')); } catch { /* first run */ }
+const byQid = new Map(existing.map((a) => [a.qid, a]));
+let added = 0, updated = 0;
+for (const a of artists) {
+ const cur = byQid.get(a.qid);
+ if (!cur) { byQid.set(a.qid, a); added++; continue; }
+ for (const m of a.museums) if (!cur.museums.includes(m)) { cur.museums.push(m); updated++; }
+ if (a.occupations.length > (cur.occupations || []).length) cur.occupations = a.occupations;
+ if (a.sitelinks > (cur.sitelinks || 0)) cur.sitelinks = a.sitelinks;
+}
+const unioned = [...byQid.values()].sort((x, y) => (y.sitelinks || 0) - (x.sitelinks || 0));
+await writeFile(rawPath, JSON.stringify(unioned, null, 2));
+console.log(`\nartists-raw.json: ${existing.length} -> ${unioned.length} (+${added} new, ${updated} museum-tag merges)`);
← faaabcc Minimal cards (signature + name only) + artist details popup
·
back to CelebritySignatures
·
Artists mega-collage (895 verified signatures, 10x90 wall + 7318273 →