← back to Rentv

admin-versions.mjs

68 lines

#!/usr/bin/env node
/* rentv-v1 — generate 10 ADMIN dashboard versions (distinct layouts over the real
   gated data) + an /admin picker. Boomer picks the admin direction he likes. $0. */
import { writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const OUT = join(dirname(fileURLToPath(import.meta.url)), 'public', 'admin');
mkdirSync(OUT, { recursive: true });

const V = [
  ['classic','Classic Dashboard','KPI cards up top, two data tables below. The safe, familiar admin.'],
  ['sidebar','Sidebar Console','Left nav rail + main panel — the SaaS-standard admin shell.'],
  ['console','Ops Console','Monospace metrics row + a live activity log. For the data-desk operator.'],
  ['cards','Module Grid','Independent widget cards you can scan at a glance.'],
  ['table','Data Table','Dense spreadsheet-style rows — power-user, keyboard-first feel.'],
  ['analytics','Analytics First','One giant headline metric, then supporting stats and a table.'],
  ['split','Master–Detail','Scrollable list on the left, KPI rail on the right.'],
  ['kanban','Kanban Board','Stories grouped into columns by sector — editorial pipeline view.'],
  ['minimal','Minimal','Quiet single-column stat sheet. No chrome, just the numbers.'],
  ['command','Command Center','Multi-panel grid — every surface (subs, feed, video, market) at once.'],
];

const GF = "https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Playfair+Display:wght@700&display=swap";
// one professional dark admin palette (versions differ by LAYOUT, not color)
const SHELL = `
:root{--a:#c8102e;--bg:#0e1116;--pan:#161b22;--ln:#242b34;--sub:#8b949e;--ink:#e6edf3}
*{box-sizing:border-box;margin:0;padding:0}body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--ink);line-height:1.5}
a{color:inherit;text-decoration:none}.wrap{max-width:1240px;margin:0 auto;padding:0 22px}
.abar{background:#0a0d11;border-bottom:1px solid var(--ln)}.abar .wrap{display:flex;justify-content:space-between;align-items:center;height:52px}
.abrand{font-family:'Playfair Display',serif;font-size:20px}.abrand span{color:var(--a)}.abrand small{font-family:'Inter';font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sub);margin-left:8px}
.abar a{font-size:12px;color:var(--sub)}.abar a:hover{color:var(--ink)}
.ah{padding:20px 0 4px}.ah h1{font-size:20px}.ah p{color:var(--sub);font-size:13px}
#app{padding:18px 0 60px;min-height:60vh}
.anav{background:var(--pan);border-bottom:1px solid var(--ln);position:sticky;top:0;z-index:20}
.anav .wrap{display:flex;gap:2px;align-items:center;height:44px}
.aham{display:none;background:none;border:0;font-size:22px;line-height:1;color:var(--ink);cursor:pointer;margin-left:auto;padding:6px 10px 6px 0}
.anl{display:flex;gap:4px;align-items:center;overflow-x:auto}
.anav a{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);padding:11px 12px;white-space:nowrap;border-bottom:3px solid transparent}
.anav a:hover{color:var(--a);border-color:var(--a)}
@media(max-width:820px){.aham{display:block}.anl{display:none;position:absolute;top:44px;left:0;right:0;background:var(--pan);flex-direction:column;align-items:stretch;border-bottom:1px solid var(--ln);box-shadow:0 12px 26px rgba(0,0,0,.4);z-index:30}.anl.open{display:flex}.anl a{padding:13px 20px;border-bottom:1px solid var(--ln)}}`;

function page(key, title, desc) {
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>RENTV Admin — ${title}</title><link href="${GF}" rel="stylesheet"><style>${SHELL}</style></head>
<body data-ver="${key}" class="ver-${key}">
<div class="abar"><div class="wrap"><div class="abrand">REN<span>TV</span> Admin<small>${title}</small></div><a href="/admin">← all 10 admin versions</a></div></div>
<div class="anav"><div class="wrap"><button class="aham" id="ahb" aria-label="Menu" aria-expanded="false">☰</button><div class="anl" id="anl"><a href="/admin">Dashboards</a><a href="/admin/publish">Publish</a><a href="/blog">Blog</a><a href="/">Main site ↗</a></div></div></div>
<div class="wrap"><div class="ah"><h1>${title}</h1><p>${desc} · live gated data</p></div><main id="app"><p style="color:var(--sub);padding:30px">Loading admin data…</p></main></div>
<script>var ahb=document.getElementById('ahb');if(ahb)ahb.addEventListener('click',function(){var o=document.getElementById('anl').classList.toggle('open');ahb.setAttribute('aria-expanded',o);});</script>
<script src="/admin/render.js"></script></body></html>`;
}

const cards = V.map(([k,t,d])=>`<a class="vc" href="/admin/${k}"><div class="tag">Admin</div><h3>${t}</h3><p>${d}</p><span class="go">Open →</span></a>`).join('');
const picker = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>RENTV — 10 Admin Versions</title><link href="${GF}" rel="stylesheet"><style>${SHELL}
.hd{padding:44px 0 6px;text-align:center}.hd h1{font-family:'Playfair Display',serif;font-size:clamp(28px,5vw,48px)}.hd h1 span{color:var(--a)}.hd p{color:var(--sub);margin-top:8px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;padding:28px 0 60px}
.vc{background:var(--pan);border:1px solid var(--ln);border-radius:10px;padding:20px;transition:.2s}
.vc:hover{transform:translateY(-4px);border-color:var(--a);box-shadow:0 16px 40px rgba(0,0,0,.5)}
.tag{font-size:10px;text-transform:uppercase;letter-spacing:.14em;color:var(--a);font-weight:800}.vc h3{font-size:20px;margin:6px 0}.vc p{color:var(--sub);font-size:14px;min-height:40px}.vc .go{color:var(--a);font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em}</style></head>
<body><div class="abar"><div class="wrap"><div class="abrand">REN<span>TV</span> Admin</div><a href="/">← main site</a></div></div>
<header class="hd"><div class="wrap"><h1>REN<span>TV</span> · 10 Admin Versions</h1><p>Ten complete back-office directions over the real subscriber / feed / video / market data. Open each and pick the one to run.</p></div></header>
<div class="wrap"><div class="grid">${cards}</div></div></body></html>`;

for (const [k,t,d] of V) writeFileSync(join(OUT, k + '.html'), page(k,t,d));
writeFileSync(join(OUT, 'index.html'), picker);
console.log('Wrote 10 admin versions + picker: ' + V.map(v=>v[0]).join(', '));