[object Object]

← 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 →