← back to Pattern Vault

scripts/curator-gallery.js

29 lines

const http=require('http'),fs=require('fs'),path=require('path');
const PORT=process.env.PORT||9955, AUTH='admin:DW2024!';
const GEN=path.join(__dirname,'..','assets','generated'), RDY=path.join(__dirname,'..','assets','spoonflower-ready');
const TB=path.join(__dirname,'..','data','trend-board.json');
function briefs(){ try{ return JSON.parse(fs.readFileSync(TB,'utf8')).briefs||[]; }catch(e){ return []; } }
function pngs(dir){ try{ return fs.readdirSync(dir).filter(f=>/\.png$/i.test(f)); }catch(e){ return []; } }
http.createServer((req,res)=>{
  if(('Basic '+Buffer.from(AUTH).toString('base64'))!==(req.headers.authorization||'')){res.writeHead(401,{'WWW-Authenticate':'Basic realm=curator'});return res.end('auth');}
  const u=new URL(req.url,'http://x');
  if(u.pathname.startsWith('/img/')){ const f=path.join(GEN,path.basename(u.pathname)); return fs.readFile(f,(e,b)=>e?(res.writeHead(404),res.end()):(res.writeHead(200,{'Content-Type':'image/png'}),res.end(b))); }
  if(u.pathname.startsWith('/rdy/')){ const f=path.join(RDY,path.basename(u.pathname)); return fs.readFile(f,(e,b)=>e?(res.writeHead(404),res.end()):(res.writeHead(200,{'Content-Type':'image/png'}),res.end(b))); }
  const bmap={}; for(const b of briefs()) bmap[b.id]=b;
  const files=pngs(GEN).sort();
  const groups={}; for(const f of files){ const id=f.replace(/_v\d+\.png$/i,''); (groups[id]=groups[id]||[]).push(f); }
  let cards='';
  for(const id of Object.keys(groups).sort()){
    const b=bmap[id]||{}; const title=b.title||id;
    cards+='<section><h2>'+title+' <span class="lane">'+(b.lane||'')+'</span> <span class="ok">✓ settlement PASS</span></h2><div class="row">';
    for(const f of groups[id]) cards+='<figure><img src="/img/'+f+'" loading="lazy"><figcaption>'+f.replace(id+'_','').replace('.png','')+'</figcaption></figure>';
    cards+='</div><p class="brief">'+(b.prompt||'')+' <b>· target '+(b.targetTier||'')+'</b></p></section>';
  }
  const rdy=pngs(RDY);
  const html='<!doctype html><meta charset=utf-8><title>Pattern Vault — Curator</title><style>body{font-family:Helvetica,Arial;margin:0;background:#faf8f3;color:#1c1a17}h1{padding:18px 26px;margin:0;border-bottom:1px solid #e4ded2;letter-spacing:.1em;text-transform:uppercase;font-size:15px}h1 small{font-weight:400;color:#8a8272;text-transform:none;letter-spacing:0}section{padding:14px 26px;border-bottom:1px solid #efe9dd}h2{font-size:15px;margin:0 0 10px}.lane{font-size:11px;color:#8a8272;font-weight:400}.ok{font-size:10px;background:#e6ede7;color:#2f5d3a;padding:2px 8px;border-radius:20px;font-weight:700}.row{display:flex;gap:14px;flex-wrap:wrap}figure{margin:0;width:260px}img{width:260px;height:260px;object-fit:cover;border-radius:10px;border:1px solid #e4ded2;background:#fff}figcaption{font-size:11px;color:#8a8272;margin-top:4px}.brief{font-size:12px;color:#6a6456;max-width:900px}.hd{padding:10px 26px;background:#2f5d3a;color:#fff;font-size:13px}</style>'
    +'<h1>Pattern Vault — Curator <small>&nbsp; original, settlement-PASS, staged (unpublished)</small></h1>'
    +'<div class=hd>'+files.length+' generated variants · '+rdy.length+' Spoonflower-ready (150 DPI, native res, no upscale)</div>'
    +cards;
  res.writeHead(200,{'Content-Type':'text/html'}); res.end(html);
}).listen(PORT,()=>console.log('curator on http://127.0.0.1:'+PORT+' (admin:DW2024!)'));