← back to Dw Fleet Registry

gradient-hero.mjs

66 lines

#!/usr/bin/env node
// Generate a UNIQUE, elegant gradient hero per site (for reference/editorial sites
// and niche-starved product sites with no photography). Hue is seeded by the slug
// so no two are alike. Rendered via headless Chrome -> public/hero-bg.jpg, committed
// on the site's branch. Marks the site in the ledger as a gradient hero.
//   node gradient-hero.mjs <slug...>

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execSync } from 'node:child_process';

const PROJECTS = path.join(os.homedir(), 'Projects');
const SELF = path.dirname(new URL(import.meta.url).pathname);
const LEDGER_PATH = path.join(SELF, 'hero-ledger.json');
const ledger = fs.existsSync(LEDGER_PATH) ? JSON.parse(fs.readFileSync(LEDGER_PATH, 'utf8')) : { usedUrls: {}, sites: {} };
const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const sh = (c, cwd) => execSync(c, { cwd, stdio: ['ignore','pipe','pipe'] }).toString().trim();

const slugs = process.argv.slice(2);
const hash = s => [...s].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);

function gradHTML(slug, seed) {
  // derive several INDEPENDENT params from the hash so two same-hue slugs still differ
  const hue = seed % 360, h2 = (hue + 28 + (seed >> 4) % 40) % 360, h3 = (hue + 300 + (seed >> 7) % 60) % 360;
  const ang = 100 + (seed >> 9) % 90;
  const b1x = 12 + (seed >> 2) % 30, b1y = 10 + (seed >> 5) % 30;
  const b2x = 60 + (seed >> 8) % 30, b2y = 60 + (seed >> 11) % 32;
  // dark luxe ground + two soft accent blooms + fine grain
  return `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
  html,body{margin:0;width:1920px;height:1200px;overflow:hidden}
  .bg{position:relative;width:1920px;height:1200px;
    background:
      radial-gradient(60% 80% at ${b1x}% ${b1y}%, hsl(${hue} 45% 22% / .9), transparent 60%),
      radial-gradient(55% 70% at ${b2x}% ${b2y}%, hsl(${h2} 40% 20% / .85), transparent 62%),
      radial-gradient(40% 50% at 70% 22%, hsl(${h3} 35% 26% / .5), transparent 60%),
      linear-gradient(${ang}deg, hsl(${hue} 30% 9%), hsl(${hue} 26% 5%) 55%, hsl(${h2} 28% 8%));}
  .grain{position:absolute;inset:0;opacity:.05;mix-blend-mode:overlay;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");}
  .vig{position:absolute;inset:0;background:radial-gradient(120% 120% at 50% 40%, transparent 55%, rgba(0,0,0,.55));}
  </style></head><body><div class="bg"><div class="grain"></div><div class="vig"></div></div></body></html>`;
}

let done = 0;
for (const slug of slugs) {
  const dir = path.join(PROJECTS, slug);
  const idx = path.join(dir, 'public', 'index.html');
  if (!fs.existsSync(idx)) { console.log(`SKIP  ${slug}  (no index.html)`); continue; }
  try {
    const seed = hash(slug);
    const hue = seed % 360;
    fs.writeFileSync('/tmp/grad.html', gradHTML(slug, seed));
    sh(`git checkout -B style-rebuild-pilot`, dir);
    execSync(`"${CHROME}" --headless=new --disable-gpu --hide-scrollbars --window-size=1920,1200 --virtual-time-budget=2500 --screenshot="${path.join(dir,'public','hero-bg.jpg')}" "file:///tmp/grad.html"`, { stdio: 'ignore' });
    // chrome writes PNG; ensure it's a jpg for the .jpg path via sips re-encode
    try { execSync(`sips -s format jpeg "${path.join(dir,'public','hero-bg.jpg')}" --out "${path.join(dir,'public','hero-bg.jpg')}" >/dev/null 2>&1`); } catch {}
    sh(`git add public/hero-bg.jpg`, dir);
    sh(`git -c user.email="steve@designerwallcoverings.com" -c user.name="Steve Abrams" commit -q -m "gradient hero (unique hue ${hue}deg) — no niche photography available"`, dir);
    ledger.sites[slug] = { gradient: true, hue };
    done++;
    console.log(`DONE  ${slug.padEnd(24)} hue ${hue}deg`);
  } catch (e) { console.log(`FAIL  ${slug}  ${String(e.message).split('\n')[0].slice(0,80)}`); }
}
fs.writeFileSync(LEDGER_PATH, JSON.stringify(ledger, null, 2) + '\n');
console.log(`\n${done}/${slugs.length} gradient heroes generated`);