← back to AsSeenInMovies
asseeninmovies: live home page — stats + 2 random-sampled rails (movies + people) + cleaner blurb grid
57e1b2051095792683416372353496476fb27ed8 · 2026-05-12 15:43:42 -0700 · SteveStudio2
Files touched
Diff
commit 57e1b2051095792683416372353496476fb27ed8
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 15:43:42 2026 -0700
asseeninmovies: live home page — stats + 2 random-sampled rails (movies + people) + cleaner blurb grid
---
server.js | 132 ++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 102 insertions(+), 30 deletions(-)
diff --git a/server.js b/server.js
index 58fa8dc..ec8461e 100644
--- a/server.js
+++ b/server.js
@@ -611,7 +611,37 @@ app.get('/people', async (req, res) => {
});
// Home — placeholder until tick 1 ships TMDB ingest + real UI.
-app.get('/', (_req, res) => {
+app.get('/', async (_req, res) => {
+ // Live-stats home with featured rails of recent movies + people. Read-only.
+ let stats = { movies_total: 0, people_total: 0, movies_poster: 0, people_headshot: 0 };
+ let featuredMovies = [], featuredPeople = [];
+ try {
+ const s = await pool.query(`
+ SELECT
+ (SELECT count(*) FROM asim_movies) AS movies_total,
+ (SELECT count(*) FROM asim_movies WHERE poster_url IS NOT NULL) AS movies_poster,
+ (SELECT count(*) FROM asim_people) AS people_total,
+ (SELECT count(*) FROM asim_people WHERE headshot_url IS NOT NULL) AS people_headshot`);
+ stats = s.rows[0];
+ const fm = await pool.query(`SELECT id, title, release_year, poster_url FROM asim_movies WHERE poster_url IS NOT NULL ORDER BY random() LIMIT 6`);
+ featuredMovies = fm.rows;
+ const fp = await pool.query(`SELECT id, full_name, headshot_url, primary_profession FROM asim_people WHERE headshot_url IS NOT NULL ORDER BY random() LIMIT 6`);
+ featuredPeople = fp.rows;
+ } catch (e) {
+ // home page should never 500; fall back to static.
+ }
+ const movieCards = featuredMovies.map(m => `
+ <a class="rail-card" href="/movie/${m.id}">
+ <div class="rail-poster" style="background-image:url('${esc(m.poster_url || '')}')"></div>
+ <div class="rail-title">${esc(m.title)}</div>
+ <div class="rail-meta">${m.release_year || '—'}</div>
+ </a>`).join('');
+ const peopleCards = featuredPeople.map(p => `
+ <a class="rail-card" href="/person/${p.id}">
+ <div class="rail-headshot" style="background-image:url('${esc(p.headshot_url || '')}')"></div>
+ <div class="rail-title">${esc(p.full_name)}</div>
+ <div class="rail-meta">${(p.primary_profession || []).slice(0,2).join(' · ')}</div>
+ </a>`).join('');
res.type('html').send(`<!doctype html>
<html lang="en"><head>
<meta charset="utf-8"><title>AsSeenInMovies</title>
@@ -620,17 +650,35 @@ app.get('/', (_req, res) => {
:root { --bg:#0d0c0a; --ink:#f0ebe3; --gold:#c9a14b; --muted:#7a706a; --line:#2a2620; }
*,*::before,*::after { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--ink); font:15px/1.6 'Inter','SF Pro Text',-apple-system,sans-serif; min-height:100vh; }
- header { padding:60px 32px 28px; text-align:center; border-bottom:1px solid var(--line); }
- h1 { margin:0 0 8px; font-family:'Cormorant Garamond',Georgia,serif; font-weight:300; font-size:clamp(36px,5vw,64px); letter-spacing:0.04em; }
+ header { padding:80px 32px 32px; text-align:center; border-bottom:1px solid var(--line); }
+ h1 { margin:0 0 8px; font-family:'Cormorant Garamond',Georgia,serif; font-weight:300; font-size:clamp(40px,5.5vw,76px); letter-spacing:0.04em; }
h1 .as { color:var(--gold); font-style:italic; font-weight:400; }
- .tag { color:var(--muted); font-size:13px; letter-spacing:0.22em; text-transform:uppercase; }
- main { max-width:1000px; margin:0 auto; padding:48px 24px 80px; display:grid; grid-template-columns:1fr 1fr; gap:32px; }
- @media (max-width:720px){ main { grid-template-columns:1fr; } }
- .card { padding:22px; border:1px solid var(--line); border-radius:10px; background:rgba(201,161,75,0.03); }
- .card h2 { margin:0 0 10px; font-size:14px; letter-spacing:0.18em; text-transform:uppercase; color:var(--gold); }
- .card p { margin:0 0 8px; color:var(--ink); }
- .card small { color:var(--muted); display:block; margin-top:8px; font-size:12px; }
- .placeholder-img { display:block; width:160px; height:213px; margin:0 auto 18px; border-radius:8px; box-shadow:0 8px 32px rgba(0,0,0,0.55); }
+ .tag { color:var(--muted); font-size:13px; letter-spacing:0.22em; text-transform:uppercase; margin-bottom:24px }
+ .cta-row { display:flex; gap:14px; justify-content:center; margin-top:18px; flex-wrap:wrap }
+ .cta { display:inline-block; padding:10px 22px; border:1px solid var(--gold); color:var(--gold); border-radius:6px; text-decoration:none; font-size:13px; letter-spacing:0.12em; text-transform:uppercase }
+ .cta:hover { background:var(--gold); color:#1a1410 }
+ .stat-row { display:flex; gap:32px; justify-content:center; margin-top:28px; flex-wrap:wrap; color:var(--muted); font-size:12px; letter-spacing:0.18em; text-transform:uppercase }
+ .stat-row b { color:var(--gold); font-weight:400; font-size:14px }
+ main { max-width:1180px; margin:0 auto; padding:60px 24px 80px }
+ .rail-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:18px; padding-bottom:8px; border-bottom:1px solid var(--line) }
+ .rail-head h2 { margin:0; font-size:14px; letter-spacing:0.18em; text-transform:uppercase; color:var(--gold) }
+ .rail-head a { color:var(--muted); font-size:12px; letter-spacing:0.12em; text-decoration:none; text-transform:uppercase }
+ .rail-head a:hover { color:var(--gold) }
+ .rail { display:grid; grid-template-columns:repeat(6, 1fr); gap:16px; margin-bottom:48px }
+ @media (max-width:900px) { .rail { grid-template-columns:repeat(3,1fr) } }
+ @media (max-width:540px) { .rail { grid-template-columns:repeat(2,1fr) } }
+ .rail-card { display:block; text-decoration:none; color:var(--ink); border:1px solid var(--line); border-radius:8px; overflow:hidden; background:rgba(201,161,75,0.03); transition:transform .15s }
+ .rail-card:hover { transform:translateY(-3px); border-color:var(--gold) }
+ .rail-poster { aspect-ratio:2/3; background:#15130f no-repeat center/cover }
+ .rail-headshot { aspect-ratio:3/4; background:#15130f no-repeat center/cover }
+ .rail-title { padding:8px 10px 2px; font-size:12.5px; line-height:1.3; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical }
+ .rail-meta { padding:0 10px 8px; color:var(--muted); font-size:10.5px; letter-spacing:0.06em }
+ .blurb-grid { display:grid; grid-template-columns:1fr 1fr; gap:24px; margin-top:24px }
+ @media (max-width:720px) { .blurb-grid { grid-template-columns:1fr } }
+ .blurb { padding:18px 20px; border:1px solid var(--line); border-radius:8px; background:rgba(201,161,75,0.02) }
+ .blurb h3 { margin:0 0 8px; font-size:13px; letter-spacing:0.14em; text-transform:uppercase; color:var(--gold) }
+ .blurb p { margin:0; color:var(--ink); font-size:13.5px }
+ .blurb small { display:block; margin-top:8px; color:var(--muted); font-size:11.5px }
code { font-family:ui-monospace,Menlo,monospace; font-size:12px; color:var(--gold); }
a { color:var(--gold); text-decoration:none; }
a:hover { text-decoration:underline; }
@@ -639,31 +687,55 @@ app.get('/', (_req, res) => {
</head>
<body>
<header>
- <img src="/img/made-with-ai.svg" class="placeholder-img" alt="" aria-hidden="true">
<h1><span class="as">As</span>Seen<span class="as">In</span>Movies</h1>
<p class="tag">a film + tv reference, built on public data</p>
+ <div class="cta-row">
+ <a class="cta" href="/movies">Browse movies →</a>
+ <a class="cta" href="/people">Browse people →</a>
+ </div>
+ <div class="stat-row">
+ <span><b>${Number(stats.movies_total).toLocaleString()}</b> movies</span>
+ <span><b>${Number(stats.movies_poster).toLocaleString()}</b> with posters</span>
+ <span><b>${Number(stats.people_total).toLocaleString()}</b> people</span>
+ <span><b>${Number(stats.people_headshot).toLocaleString()}</b> with headshots</span>
+ </div>
</header>
<main>
- <div class="card">
- <h2>What this is</h2>
- <p>An open IMDb-class reference for films and television — movie facts, cast and crew, credits — built from <em>only</em> public data sources (TMDB, Wikidata, OMDb, MovieLens, public union directories).</p>
- <p>Tied into <a href="https://thesetdecorator.com">thesetdecorator.com</a> for the set-decorator roster and into the DW catalog for the SPOTTED feature.</p>
- <small>No visual assets are sourced from IMDb. Posters and headshots that aren't rights-cleared use a "made with AI" placeholder.</small>
- </div>
- <div class="card">
- <h2>SPOTTED</h2>
- <p>Find the actual products you see on screen — wallpaper, lamps, props, wardrobe — with direct links to where you can buy them. Tagged by set decorators, verified by claim-holders.</p>
- <small>See <code>/api/spotted</code> for the live feed.</small>
+ ${featuredMovies.length ? `
+ <div class="rail-head">
+ <h2>Featured movies</h2>
+ <a href="/movies">Browse all →</a>
</div>
- <div class="card">
- <h2>Premium profiles</h2>
- <p>Every actor, actress, and crew member has a basic public page built from public credits. Verified union members and their representation can upgrade to a flagship profile — reels, current projects, contact form, custom bio.</p>
- <small>Upgrade flow + Stripe wiring lands in a later tick.</small>
+ <div class="rail">${movieCards}</div>` : ''}
+
+ ${featuredPeople.length ? `
+ <div class="rail-head">
+ <h2>Featured people</h2>
+ <a href="/people">Browse all →</a>
</div>
- <div class="card">
- <h2>Status</h2>
- <p>Tick 0 scaffold — schema, server, search/movie/person/SPOTTED endpoints. Tick 1 begins TMDB ingest. Schedule + plan in <code>/docs/PLAN.md</code>.</p>
- <small>API: <code>/api/health</code>, <code>/api/search?q=</code>, <code>/api/movie/:id</code>, <code>/api/person/:id</code>, <code>/api/spotted</code></small>
+ <div class="rail">${peopleCards}</div>` : ''}
+
+ <div class="blurb-grid">
+ <div class="blurb">
+ <h3>What this is</h3>
+ <p>An open IMDb-class reference built from <em>only</em> public data — TMDB, Wikidata, OMDb, MovieLens, and public union directories. Tied to <a href="https://thesetdecorator.com">thesetdecorator.com</a> for set-decorator credits and to the DW catalog for SPOTTED product placements.</p>
+ <small>No visual assets are sourced from IMDb. Posters and headshots that aren't rights-cleared use a "made with AI" placeholder.</small>
+ </div>
+ <div class="blurb">
+ <h3>SPOTTED</h3>
+ <p>Find the actual products you see on screen — wallpaper, lamps, props, wardrobe — tagged by set decorators and verified by claim-holders.</p>
+ <small>Live feed: <code>/api/spotted</code></small>
+ </div>
+ <div class="blurb">
+ <h3>Premium profiles</h3>
+ <p>Every actor, actress, and crew member has a basic public page built from public credits. Verified union members and their representation can upgrade to a flagship profile — reels, current projects, contact form, custom bio.</p>
+ <small>Stripe upgrade flow ships in a later tick.</small>
+ </div>
+ <div class="blurb">
+ <h3>API</h3>
+ <p>Read-only endpoints for movie metadata, people, search, and SPOTTED. Public-data-only — no IMDb scrape, no paywalled feeds.</p>
+ <small><code>/api/health</code> · <code>/api/search?q=</code> · <code>/api/movie/:id</code> · <code>/api/person/:id</code> · <code>/api/spotted</code></small>
+ </div>
</div>
</main>
<footer>
← 8205ad7 wikidata-enrich: add --byCast (filter to role=actor) to targ
·
back to AsSeenInMovies
·
asseeninmovies: /search HTML page — combined movies+people g 562f3b6 →