← back to Wallco Ai
drunk-animals: /drunk-animals/live realtime viewer + JSON feed
25d68d1eabdacfc3b9d337bd14dd26ee1ca3ace9 · 2026-05-12 22:52:26 -0700 · SteveStudio2
- GET /api/drunk-animals/feed?since_id=&limit= — JSON feed of newest
drunk-animal designs (id/title/image_url/dominant_hex/motifs/url)
- GET /drunk-animals/live — dark immersive page, polls feed every
30s, prepends new designs with slide-in animation + green outline
highlight, countdown to next 4-min tick in header.
Smoke-tested: feed returns 83 total, live header renders pulse +
count pill + countdown to next tick.
Files touched
Diff
commit 25d68d1eabdacfc3b9d337bd14dd26ee1ca3ace9
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 22:52:26 2026 -0700
drunk-animals: /drunk-animals/live realtime viewer + JSON feed
- GET /api/drunk-animals/feed?since_id=&limit= — JSON feed of newest
drunk-animal designs (id/title/image_url/dominant_hex/motifs/url)
- GET /drunk-animals/live — dark immersive page, polls feed every
30s, prepends new designs with slide-in animation + green outline
highlight, countdown to next 4-min tick in header.
Smoke-tested: feed returns 83 total, live header renders pulse +
count pill + countdown to next tick.
---
server.js | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 133 insertions(+)
diff --git a/server.js b/server.js
index d23c333..8d01ce0 100644
--- a/server.js
+++ b/server.js
@@ -2191,6 +2191,139 @@ app.get('/drunk-animals', (req, res) => {
res.redirect(302, '/designs?' + qs);
});
+// ── JSON feed for the /drunk-animals/live page — newest 60 designs with
+// just the fields the viewer needs (id/title/image_url/dominant_hex/prompt).
+app.get('/api/drunk-animals/feed', (req, res) => {
+ res.setHeader('Cache-Control', 'no-store');
+ const sinceId = parseInt(req.query.since_id || '0', 10) || 0;
+ const limit = Math.min(60, Math.max(1, parseInt(req.query.limit || '60', 10)));
+ const rows = DESIGNS
+ .filter(d => d.category === 'drunk-animals' && d.id > sinceId)
+ .sort((a, b) => b.id - a.id)
+ .slice(0, limit)
+ .map(d => ({
+ id: d.id,
+ title: d.title,
+ handle: d.handle,
+ image_url: d.image_url,
+ dominant_hex: d.dominant_hex,
+ created_at: d.created_at,
+ motifs: d.motifs || [],
+ url: `/design/${d.id}`,
+ }));
+ res.json({ count: rows.length, total: DESIGNS.filter(d => d.category === 'drunk-animals').length, designs: rows });
+});
+
+// ── /drunk-animals/live — auto-polling stream view of the overnight run.
+// New designs slide in at the top every 30s. Dark immersive theme matches
+// the collection's botanical-pop-Warhol aesthetic.
+app.get('/drunk-animals/live', (_req, res) => {
+ res.type('html').send(`<!doctype html>
+<html lang="en"><head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Drunk Animals · Live · wallco.ai</title>
+<style>
+:root { --bg:#0e0612; --fg:#fff; --pink:#ff79c6; --green:#50fa7b; --line:rgba(255,255,255,.12); }
+* { box-sizing:border-box }
+body { margin:0; background:var(--bg); color:var(--fg); font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Roboto,sans-serif; min-height:100vh }
+header { position:sticky; top:0; z-index:10; padding:14px 20px; background:linear-gradient(180deg,rgba(14,6,18,.96),rgba(14,6,18,.7)); backdrop-filter:blur(12px); border-bottom:1px solid var(--line); display:flex; align-items:center; gap:14px; flex-wrap:wrap }
+header h1 { font-family:'Playfair Display','Times New Roman',serif; font-weight:400; font-size:22px; margin:0; line-height:1 }
+header .meta { display:flex; gap:8px; align-items:center; font-size:12px; color:#e6d4f0 }
+header .pill { padding:3px 10px; border-radius:999px; border:1px solid var(--pink); color:var(--pink) }
+header .pill.green { border-color:var(--green); color:var(--green) }
+header .pulse::before { content:""; display:inline-block; width:7px; height:7px; background:var(--green); border-radius:50%; margin-right:6px; animation:pulse 1.6s ease-in-out infinite }
+@keyframes pulse { 0%,100%{opacity:.4} 50%{opacity:1} }
+header a { color:#e6d4f0; text-decoration:none; font-size:12px; padding:4px 10px; border:1px solid var(--line); border-radius:999px }
+header a:hover { border-color:var(--pink); color:var(--pink) }
+main { padding:20px; max-width:1500px; margin:0 auto }
+.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:18px }
+.card { background:#1a0f1a; border:1px solid var(--line); border-radius:14px; overflow:hidden; position:relative; opacity:0; transform:translateY(-12px); animation:slidein .5s ease forwards }
+@keyframes slidein { to { opacity:1; transform:translateY(0) } }
+.card a { display:block; text-decoration:none; color:inherit }
+.card .img { aspect-ratio:1/1; background-size:cover; background-position:center }
+.card .meta { padding:12px 14px }
+.card .meta .title { font-family:'Playfair Display',serif; font-size:17px; line-height:1.2; margin:0 0 6px }
+.card .meta .id { font-size:11px; opacity:.55; letter-spacing:.06em }
+.card .dot { display:inline-block; width:10px; height:10px; border-radius:50%; vertical-align:middle; margin-left:6px; border:1px solid rgba(255,255,255,.2) }
+.card .motifs { display:flex; flex-wrap:wrap; gap:4px; margin-top:8px }
+.card .motifs span { font-size:10px; padding:2px 7px; background:rgba(255,121,198,.15); color:var(--pink); border-radius:999px; text-transform:lowercase }
+.card.new { box-shadow:0 0 0 2px var(--green), 0 8px 28px rgba(80,250,123,.25) }
+.empty { text-align:center; padding:80px 20px; opacity:.5 }
+</style>
+</head><body>
+<header>
+ <h1>The Drunk Animals Collection</h1>
+ <span class="meta pulse">live</span>
+ <span class="pill" id="count">0 designs</span>
+ <span class="pill green" id="next">next in —</span>
+ <a href="/drunk-animals">Grid view</a>
+ <a href="/">wallco.ai</a>
+</header>
+<main>
+ <div class="grid" id="grid"></div>
+ <div class="empty" id="empty">Loading the menagerie…</div>
+</main>
+<script>
+const grid = document.getElementById('grid');
+const empty = document.getElementById('empty');
+const countEl = document.getElementById('count');
+const nextEl = document.getElementById('next');
+let maxId = 0;
+let lastTickAt = Date.now();
+
+function card(d, isNew) {
+ const motifs = (d.motifs || []).slice(0, 6).map(m => '<span>' + m + '</span>').join('');
+ return \`<article class="card\${isNew?' new':''}">
+ <a href="\${d.url}" target="_blank" rel="noopener">
+ <div class="img" style="background-image:url('\${d.image_url}')"></div>
+ <div class="meta">
+ <p class="title">\${d.title}<span class="dot" style="background:\${d.dominant_hex||'#888'}"></span></p>
+ <p class="id">#\${d.id} · \${(d.created_at||'').slice(11,16)} UTC</p>
+ <div class="motifs">\${motifs}</div>
+ </div>
+ </a>
+ </article>\`;
+}
+
+async function poll() {
+ try {
+ const r = await fetch('/api/drunk-animals/feed?since_id=' + maxId);
+ const j = await r.json();
+ countEl.textContent = j.total + ' design' + (j.total === 1 ? '' : 's') + ' · 1 every 4 min';
+ if (j.designs.length === 0 && maxId === 0) {
+ empty.textContent = 'No designs yet — generator just started';
+ return;
+ }
+ empty.style.display = 'none';
+ // Prepend new
+ if (j.designs.length) {
+ const isFirstLoad = maxId === 0;
+ const fragHtml = j.designs.map(d => card(d, !isFirstLoad)).join('');
+ grid.insertAdjacentHTML('afterbegin', fragHtml);
+ maxId = Math.max(maxId, ...j.designs.map(d => d.id));
+ if (!isFirstLoad) lastTickAt = Date.now();
+ }
+ } catch (e) {
+ console.error('feed poll failed', e);
+ }
+}
+
+function updateNext() {
+ // We expect a new design every 4 min from the daemon. Show countdown.
+ const ms = 4*60*1000 - (Date.now() - lastTickAt);
+ if (ms <= 0) { nextEl.textContent = 'next any moment'; return; }
+ const m = Math.floor(ms / 60000), s = Math.floor((ms % 60000) / 1000);
+ nextEl.textContent = 'next in ' + m + ':' + String(s).padStart(2,'0');
+}
+
+poll();
+setInterval(poll, 30_000);
+setInterval(updateNext, 1000);
+</script>
+</body></html>`);
+});
+
// ── DESIGNS CATALOG (full grid with sort + density + category filter)
app.get('/designs', (req, res) => {
// Conditional GET: emit Last-Modified from the freshest design in the
← 83c5b98 drunk-animals: cross-tick prompt dedup against last 5 PG row
·
back to Wallco Ai
·
drunk-animals: bump dedup window 5 to 8 4dd3794 →