← back to Dw Marketing Viewer
server.js
198 lines
#!/usr/bin/env node
// DW Marketing — Wallpaper's Back social command viewer
// Style matches dw-staged-active-viewer (:9846). Zero deps (raw http).
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 9847;
const AVATAR_DIR = path.join(__dirname, 'avatars');
const DEFAULT_AVATAR = '/avatars/avatar-wpb-dark.png';
const WPB = 'https://wallpapersback.com';
const IMG = '/designs/img/';
const ACCOUNTS = [
{
platform: 'TikTok', icon: '🎵', handle: 'wallpapersback',
url: 'https://www.tiktok.com/@wallpapersback',
status: 'live', statusNote: 'Existing TikTok Shop account — logged in',
pwLast4: '911*', envKey: 'WPB_TIKTOK_*',
bioLimit: 80,
bio: "🌿 Original wallpaper, reborn daily — fresh AI patterns for your walls. Shop ↓",
},
{
platform: 'Instagram', icon: '📷', handle: 'wallpaperisback',
url: 'https://www.instagram.com/wallpaperisback',
status: 'live', statusNote: 'Created 2026-06-23 · verified',
pwLast4: '911*', envKey: 'WPB_INSTAGRAM_*',
bioLimit: 150,
bio: "Wallpaper's Back 🌿✨\nOriginal AI-crafted wallpaper patterns — new designs daily.\nPeel · paste · transform your space.\n🛕 Shop the collection ↓",
},
{
platform: 'X / Twitter', icon: '✕', handle: 'WallpaperBack',
url: 'https://x.com/WallpaperBack',
status: 'live', statusNote: 'Created on phone 2026-06-23 (web automation blocked)',
pwLast4: '911*', envKey: 'WPB_X_*',
bioLimit: 160,
bio: "Original wallpaper, reborn. 🌿 AI-crafted patterns + murals, new daily. Transform any wall → wallpapersback.com",
},
];
const SHARED = { email: 'info@wallpapersback.com', phone: '310-713-0489', dob: 'Jan 3, 1971', linkInBio: 'https://wallpapersback.com' };
const CAROUSEL = {
images: [
'1781036121748_1084855833.png','1781036020017_47709849.png','1781035921400_2016604802.png',
'1781035821844_1409088740.png','1781035653657_1404626502.png','1781035651286_493073813.png',
'1781034900507_1678143317.png','1780549976580_1965836513.png','1780549072364_1488626861.png',
'1780510863396_1320598381.png',
],
caption: "Wallpaper's back. 🌿 Ten fresh patterns, hot off the press — which one's going on your wall?\n#wallpaper #wallpapersback #homedecor #interiordesign #maximalist #accentwall #wallpaperdesign #patterndesign #interiorinspo #homedesign",
status: 'ready', statusNote: 'Prepped — post via TikTok app ➕ → Photo',
};
function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
function page(){
const accCards = ACCOUNTS.map(a=>`
<div class="card acct">
<div class="ahd">
<span class="plat"><img class="av" src="${DEFAULT_AVATAR}" alt="">${a.icon} ${esc(a.platform)}</span>
<span class="pill ${a.status}">${a.status}</span>
</div>
<a class="handle" href="${a.url}" target="_blank" rel="noopener noreferrer">@${esc(a.handle)}</a>
<div class="anote">${esc(a.statusNote)}</div>
<div class="biobox">
<div class="biolbl">BIO <span class="cnt${a.bio.length>a.bioLimit?' over':''}">${a.bio.length}/${a.bioLimit}</span></div>
<div class="bio">${esc(a.bio).replace(/\n/g,'<br>')}</div>
</div>
<div class="meta">
<span class="kv">🔑 …${esc(a.pwLast4)}</span>
<span class="kv mono">${esc(a.envKey)}</span>
</div>
</div>`).join('');
const thumbs = CAROUSEL.images.map((f,i)=>`
<a class="ct" href="${WPB}${IMG}${f}" target="_blank" rel="noopener noreferrer"
style="background-image:url('${WPB}${IMG}${f}')"><span class="cn">${i+1}</span></a>`).join('');
return `<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>DW Marketing — Wallpaper's Back</title>
<link rel="icon" type="image/png" href="/avatars/favicon.png">
<style>
:root { --bg:#0f1115; --card:#191c23; --ink:#e7e9ee; --mut:#8b91a0; --line:#2a2e38; --accent:#6ea8fe; }
*{box-sizing:border-box;}
body{margin:0;font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--ink);}
header{position:sticky;top:0;z-index:10;background:#12151c;border-bottom:1px solid var(--line);padding:12px 18px;}
h1{font-size:16px;margin:0 0 6px;font-weight:650;}
.totals{color:var(--mut);font-size:13px;}
.totals b{color:var(--ink);}
.tabs{display:flex;gap:6px;margin-top:10px;}
.tab{cursor:pointer;border:1px solid var(--line);background:#1b1f28;color:var(--mut);padding:6px 16px;border-radius:8px;font-size:13px;font-weight:600;}
.tab.active{border-color:var(--accent);background:#22304a;color:#fff;}
main{padding:16px 18px 60px;}
section{display:none;} section.active{display:block;}
.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;}
@media(max-width:820px){.grid{grid-template-columns:1fr;}}
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 13px;display:flex;flex-direction:column;gap:8px;}
.ahd{display:flex;justify-content:space-between;align-items:center;}
.plat{font-weight:650;font-size:14px;display:flex;align-items:center;gap:8px;}
.av{width:30px;height:30px;border-radius:50%;object-fit:cover;border:1px solid var(--line);}
.avopts{display:flex;gap:16px;align-items:center;margin:14px 0 2px;}
.avopt{display:flex;flex-direction:column;align-items:center;gap:5px;}
.avopt img{width:84px;height:84px;border-radius:50%;object-fit:cover;border:2px solid var(--line);}
.avopt.pick img{border-color:var(--accent);}
.avopt span{font-size:11px;color:var(--mut);}
.avlbl{color:var(--mut);font-size:12px;font-weight:600;}
.pill{font-size:10px;border-radius:5px;padding:1px 7px;text-transform:uppercase;font-weight:700;letter-spacing:.04em;}
.pill.live{color:#9be59b;border:1px solid #1f5c17;background:#122a12;}
.pill.ready{color:#ffd479;border:1px solid #5c4a17;background:#241d0a;}
.handle{color:var(--accent);text-decoration:none;font-size:15px;font-weight:600;}
.handle:hover{text-decoration:underline;}
.anote{color:var(--mut);font-size:11.5px;}
.biobox{background:#12151c;border:1px solid var(--line);border-radius:8px;padding:8px 9px;}
.biolbl{color:var(--mut);font-size:10px;font-weight:700;letter-spacing:.05em;margin-bottom:4px;display:flex;justify-content:space-between;}
.biolbl .cnt{color:#9be59b;} .biolbl .cnt.over{color:#ff8a8a;}
.bio{font-size:12.5px;line-height:1.5;color:#dfe3ec;}
.meta{display:flex;gap:10px;align-items:center;}
.kv{font-size:11px;color:var(--mut);} .mono{font-family:ui-monospace,Menlo,monospace;color:#aab3c5;}
.sharebar{display:flex;flex-wrap:wrap;gap:14px;margin:14px 0 4px;color:var(--mut);font-size:12.5px;}
.sharebar b{color:var(--ink);} .sharebar a{color:var(--accent);text-decoration:none;}
.cgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin:12px 0;}
.ct{aspect-ratio:1/1;background:#0c0e13 center/cover no-repeat;border:1px solid var(--line);border-radius:8px;position:relative;display:block;}
.ct .cn{position:absolute;top:4px;left:4px;background:rgba(0,0,0,.6);color:#fff;font-size:11px;font-weight:700;border-radius:5px;padding:0 6px;}
.capbox{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px;margin-top:8px;}
.capbox h3{margin:0 0 6px;font-size:13px;color:var(--mut);font-weight:600;}
.cap{white-space:pre-wrap;font-size:13px;line-height:1.55;color:#dfe3ec;}
.steps{margin:10px 0 0;padding-left:18px;color:#cdd6e8;font-size:12.5px;line-height:1.7;}
.projnote{color:#ffd479;font-size:11.5px;background:#241d0a;border:1px solid #5c4a17;border-radius:6px;padding:5px 9px;margin-bottom:12px;display:inline-block;}
</style></head><body>
<header>
<h1>🌿 DW Marketing — Wallpaper's Back</h1>
<div class="totals"><b>${ACCOUNTS.length}</b> social accounts · <b>1</b> carousel queued · brand <b>wallpapersback.com</b></div>
<div class="tabs">
<div class="tab active" data-t="accounts">Accounts</div>
<div class="tab" data-t="carousel">Carousel</div>
</div>
</header>
<main>
<section id="accounts" class="active">
<div class="grid">${accCards}</div>
<div class="sharebar">
<span>🔗 Link-in-bio: <a href="${SHARED.linkInBio}" target="_blank" rel="noopener noreferrer">${SHARED.linkInBio}</a></span>
<span>✉️ <b>${SHARED.email}</b></span>
<span>📱 <b>${SHARED.phone}</b></span>
<span>🎂 ${SHARED.dob}</span>
</div>
<div class="avopts">
<span class="avlbl">Brand mark (Optima):</span>
<div class="avopt pick"><img src="/avatars/avatar-wpb-dark.png" alt=""><span>dark ✓</span></div>
<div class="avopt"><img src="/avatars/avatar-wpb-light.png" alt=""><span>light</span></div>
<div class="avopt"><img src="/avatars/avatar-wpb-green.png" alt=""><span>green</span></div>
</div>
<div class="projnote">Passwords stored in secrets registry (WPB_* keys) — shown as last-4 only.</div>
</section>
<section id="carousel">
<div class="projnote">${esc(CAROUSEL.statusNote)}</div>
<div class="cgrid">${thumbs}</div>
<div class="capbox">
<h3>CAPTION</h3>
<div class="cap">${esc(CAROUSEL.caption)}</div>
</div>
<div class="capbox">
<h3>POST STEPS (TikTok app)</h3>
<ol class="steps">
<li>Save the 10 images above to your phone (tap → opens full-res)</li>
<li>TikTok app → ➕ → switch mode to <b>Photo</b></li>
<li>Select all 10 in order → Next</li>
<li>Add sound (trending/on-brand) → Next</li>
<li>Paste caption → <b>Post</b></li>
</ol>
</div>
</section>
</main>
<script>
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('section').forEach(x=>x.classList.remove('active'));
t.classList.add('active'); document.getElementById(t.dataset.t).classList.add('active');
});
</script>
</body></html>`;
}
http.createServer((req,res)=>{
if(req.url==='/healthz'){res.writeHead(200,{'Content-Type':'text/plain'});return res.end('ok');}
if(req.url.startsWith('/avatars/')){
const f=path.join(AVATAR_DIR, path.basename(req.url));
if(f.startsWith(AVATAR_DIR) && fs.existsSync(f)){
res.writeHead(200,{'Content-Type':'image/png','Cache-Control':'no-cache'});
return res.end(fs.readFileSync(f));
}
res.writeHead(404); return res.end('not found');
}
res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});
res.end(page());
}).listen(PORT,'127.0.0.1',()=>console.log('DW Marketing viewer → http://127.0.0.1:'+PORT+'/'));