← back to Nineoh Guide
beverly hills photos: 8 free-licensed CC/PD city photos (Rodeo Drive, the sign) into rights-ledger; home 'Beverly Hills backdrop' strip w/ attribution
8fd5a564a6bd86067cda6e527352c3108f82ad25 · 2026-07-25 11:40:26 -0700 · Steve
Files touched
M apps/web/app/page.tsxA db/ingest-beverlyhills-photos.mjs
Diff
commit 8fd5a564a6bd86067cda6e527352c3108f82ad25
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jul 25 11:40:26 2026 -0700
beverly hills photos: 8 free-licensed CC/PD city photos (Rodeo Drive, the sign) into rights-ledger; home 'Beverly Hills backdrop' strip w/ attribution
---
apps/web/app/page.tsx | 60 ++++++++++++++++++++++++++
db/ingest-beverlyhills-photos.mjs | 90 +++++++++++++++++++++++++++++++++++++++
2 files changed, 150 insertions(+)
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index c5f5662..b6d48cc 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -24,8 +24,22 @@ async function getStats() {
}
}
+async function getScenery() {
+ try {
+ const { rows } = await pool.query(
+ `select file_url, caption, attribution_text
+ from assets where rights_notes = 'beverly-hills-location'
+ order by created_at limit 6`
+ );
+ return rows;
+ } catch {
+ return [];
+ }
+}
+
export default async function Home() {
const { episodes, cast, recapped } = await getStats();
+ const scenery = await getScenery();
const jsonLd = {
"@context": "https://schema.org",
"@type": "TVSeries",
@@ -68,6 +82,52 @@ export default async function Home() {
<NavCard href="/search" title="Search" sub="Find any episode, character, or moment" />
</div>
+ {scenery.length > 0 && (
+ <>
+ <h2>The Beverly Hills backdrop</h2>
+ <p style={{ color: "var(--muted)", fontSize: 13, marginTop: -4 }}>
+ The real 90210 — photographed by the Wikimedia Commons community, used
+ under free licenses.
+ </p>
+ <div
+ style={{
+ display: "grid",
+ gap: 10,
+ gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))",
+ marginTop: 12,
+ }}
+ >
+ {scenery.map((s) => (
+ <figure
+ key={s.file_url}
+ style={{
+ margin: 0,
+ borderRadius: "var(--radius)",
+ overflow: "hidden",
+ border: "1px solid var(--line)",
+ boxShadow: "var(--shadow)",
+ background: "var(--paper-2)",
+ }}
+ >
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+ <img
+ src={s.file_url}
+ alt={s.caption ?? "Beverly Hills"}
+ loading="lazy"
+ style={{ width: "100%", height: 130, objectFit: "cover", display: "block" }}
+ />
+ <figcaption style={{ padding: "6px 8px" }}>
+ <div style={{ fontSize: 12, fontWeight: 600 }}>{s.caption}</div>
+ <div style={{ fontSize: 10, color: "var(--muted)" }}>
+ {s.attribution_text}
+ </div>
+ </figcaption>
+ </figure>
+ ))}
+ </div>
+ </>
+ )}
+
{episodes === 0 && (
<p style={{ marginTop: 20, color: "var(--maroon)", fontSize: 13 }}>
Content is being curated — recaps and profiles will populate shortly.
diff --git a/db/ingest-beverlyhills-photos.mjs b/db/ingest-beverlyhills-photos.mjs
new file mode 100644
index 0000000..eba2493
--- /dev/null
+++ b/db/ingest-beverlyhills-photos.mjs
@@ -0,0 +1,90 @@
+// Pulls atmospheric photos of BEVERLY HILLS (the city — Rodeo Drive, the sign,
+// palm streets), NOT the show. License-verified free-for-reuse only (PD/CC0/CC-BY/
+// CC-BY-SA). Stored in assets tagged 'beverly-hills-location' with attribution.
+// Run: node db/ingest-beverlyhills-photos.mjs
+import pg from "pg";
+
+const DATABASE_URL =
+ process.env.DATABASE_URL ?? "postgresql://localhost/nineoh_guide?host=/tmp";
+const pool = new pg.Pool({ connectionString: DATABASE_URL });
+const UA = "nineoh-guide/0.1 (unofficial fan project; contact: set-at-launch)";
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+const stripHtml = (s) => (s || "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
+
+const FREE = /^(cc0|cc-by-\d|cc-by-sa-\d|pd|public domain)/i;
+const NONFREE = /(nc|nd|non-free|fair use)/i;
+
+async function fetchJson(url, tries = 4) {
+ for (let i = 0; i < tries; i++) {
+ try {
+ const r = await fetch(url, { headers: { "User-Agent": UA } });
+ return JSON.parse(await r.text());
+ } catch {
+ await sleep(800 * (i + 1));
+ }
+ }
+ throw new Error("rate-limited");
+}
+
+const TARGET = 8;
+const SEARCHES = [
+ "Rodeo Drive Beverly Hills",
+ "Beverly Hills sign California",
+ "Beverly Hills City Hall",
+ "Beverly Hills palm trees street",
+ "Beverly Hills California",
+];
+
+let stored = 0;
+for (const q of SEARCHES) {
+ if (stored >= TARGET) break;
+ const u =
+ `https://commons.wikimedia.org/w/api.php?action=query&format=json&generator=search` +
+ `&gsrsearch=${encodeURIComponent(q)}&gsrnamespace=6&gsrlimit=8` +
+ `&prop=imageinfo&iiprop=extmetadata|url&iiurlwidth=900`;
+ let j;
+ try {
+ j = await fetchJson(u);
+ } catch {
+ continue;
+ }
+ const pages = Object.values(j.query?.pages ?? {});
+ for (const page of pages) {
+ if (stored >= TARGET) break;
+ const info = page.imageinfo?.[0];
+ if (!info) continue;
+ const m = info.extmetadata ?? {};
+ const licName = m.LicenseShortName?.value || "";
+ const lic = (m.License?.value || "").toLowerCase();
+ const free = FREE.test(lic) || /public domain|cc0|cc by(?!-nc|-nd)/i.test(licName);
+ if (!free || NONFREE.test(lic) || /nc|nd|fair use/i.test(licName)) continue;
+ // Skip maps/logos/diagrams — want photos.
+ const title = (page.title || "").replace(/^File:/, "");
+ if (/\.svg$|map|logo|seal|diagram|\.pdf$/i.test(title)) continue;
+
+ const url = info.thumburl || info.url;
+ const dup = await pool.query(`select 1 from assets where file_url=$1`, [url]);
+ if (dup.rowCount) continue;
+
+ const licType = /cc0|public domain|^pd/i.test(lic) ? "public-domain" : /sa/i.test(lic) ? "cc-by-sa" : "cc-by";
+ await pool.query(
+ `insert into assets (type, file_url, thumbnail_url, license_type, license_url,
+ attribution_text, usage_scope, source_url, rights_notes, caption, approved_for_marketing)
+ values ('image',$1,$1,$2,$3,$4,'editorial',$5,'beverly-hills-location',$6,false)`,
+ [
+ url,
+ licType,
+ m.LicenseUrl?.value || null,
+ `${stripHtml(m.Artist?.value || m.Credit?.value || "Unknown")} / ${licName} (via Wikimedia Commons)`,
+ `https://commons.wikimedia.org/wiki/${encodeURIComponent(page.title)}`,
+ title.replace(/\.[a-z]+$/i, "").replace(/_/g, " ").slice(0, 80),
+ ]
+ );
+ stored++;
+ console.log(`OK ${licType.padEnd(13)} ${title.slice(0, 60)}`);
+ }
+ await sleep(700);
+}
+
+console.log(`\nstored ${stored} Beverly Hills location photos (free-licensed).`);
+await pool.end();
← 73371b3 cast photos: 12/14 headshots from Wikimedia (per-file licens
·
back to Nineoh Guide
·
mobile forward: easter eggs — tap UNOFFICIAL badge 5x for hi c7a0053 →