← back to Agent Ad Network
Add DW-network web display surface: /serve/display + /embed.js + /cp promo clicks + DW cross-promo pool (all ad links rel=nofollow sponsored)
d6af20ebb62f48cb596ae814ac086394504f7404 · 2026-08-04 11:09:05 -0700 · Steve Abrams
Files touched
A assets/embed.jsM server.js
Diff
commit d6af20ebb62f48cb596ae814ac086394504f7404
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 11:09:05 2026 -0700
Add DW-network web display surface: /serve/display + /embed.js + /cp promo clicks + DW cross-promo pool (all ad links rel=nofollow sponsored)
---
assets/embed.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
server.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 123 insertions(+)
diff --git a/assets/embed.js b/assets/embed.js
new file mode 100644
index 0000000..5a5962d
--- /dev/null
+++ b/assets/embed.js
@@ -0,0 +1,62 @@
+/* DW Network display-ad embed — one line per site:
+ <script src="https://ads.agentabrams.com/embed.js"
+ data-slot="footer-strip|in-grid-card|sidebar-rail"
+ data-site="silkwallpaper.com" data-context="silk,wallcovering"></script>
+ Renders a "Sponsored" unit; every ad link is rel="nofollow sponsored" (never leaks
+ equity / never reads as a link network — see dw-fleet-seo-independent-ranking). */
+(function () {
+ var s = document.currentScript;
+ if (!s) return;
+ var slot = (s.getAttribute('data-slot') || 'footer-strip').toLowerCase();
+ var site = s.getAttribute('data-site') || location.hostname;
+ var ctx = s.getAttribute('data-context') || '';
+ var origin = new URL(s.src).origin;
+ var url = origin + '/serve/display?format=json&slot=' + encodeURIComponent(slot) +
+ '&site=' + encodeURIComponent(site) + '&context=' + encodeURIComponent(ctx);
+
+ fetch(url, { credentials: 'omit' }).then(function (r) { return r.json(); }).then(function (d) {
+ var ad = d && d.ad;
+ if (!ad) return; // fail-open: no fill → render nothing
+ var wrap = document.createElement('div');
+ wrap.className = 'dwad dwad-' + slot;
+ var link = '<a href="' + ad.clickUrl + '" target="_blank" rel="nofollow sponsored noopener">' +
+ '<span class="dwad-h">' + esc(ad.headline) + '</span> ' +
+ '<span class="dwad-b">' + esc(ad.body) + '</span></a>';
+ wrap.innerHTML =
+ '<span class="dwad-label">Sponsored</span>' + link +
+ '<span class="dwad-by">' + esc(ad.sponsor || '') + '</span>' +
+ (slot !== 'footer-strip' ? '<button class="dwad-x" aria-label="dismiss">×</button>' : '');
+
+ injectStyles(origin);
+ place(wrap, slot, s);
+ var x = wrap.querySelector('.dwad-x');
+ if (x) x.onclick = function () { wrap.remove(); };
+ }).catch(function () {});
+
+ function esc(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, function (c) {
+ return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
+
+ function place(wrap, slot, script) {
+ if (slot === 'in-grid-card') {
+ // drop where the script tag sits (author places it inside the grid)
+ script.parentNode.insertBefore(wrap, script.nextSibling);
+ } else {
+ document.body.appendChild(wrap); // footer-strip / sidebar-rail = fixed via CSS
+ }
+ }
+
+ function injectStyles(origin) {
+ if (document.getElementById('dwad-css')) return;
+ var css = document.createElement('style'); css.id = 'dwad-css';
+ css.textContent =
+ '.dwad{font:13px/1.4 system-ui,sans-serif;color:#222;background:#faf7f0;border:1px solid #e6ddc8;box-sizing:border-box}' +
+ '.dwad a{color:#8b6914;text-decoration:none}.dwad a:hover{text-decoration:underline}' +
+ '.dwad-label{font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:#a99;margin-right:8px}' +
+ '.dwad-h{font-weight:600}.dwad-by{font-size:11px;color:#a99;margin-left:8px}' +
+ '.dwad-x{border:0;background:none;font-size:16px;color:#a99;cursor:pointer;float:right;line-height:1}' +
+ '.dwad-footer-strip{position:fixed;left:0;right:0;bottom:0;z-index:9998;padding:8px 16px;text-align:center;box-shadow:0 -1px 6px rgba(0,0,0,.06)}' +
+ '.dwad-sidebar-rail{position:fixed;right:16px;bottom:16px;z-index:9998;max-width:280px;padding:12px;border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.12)}' +
+ '.dwad-in-grid-card{padding:16px;border-radius:8px;min-height:120px;display:flex;flex-direction:column;justify-content:center}';
+ document.head.appendChild(css);
+ }
+})();
diff --git a/server.js b/server.js
index 569af7f..2a8513e 100644
--- a/server.js
+++ b/server.js
@@ -209,6 +209,36 @@ function pickAd(contextWords) {
return scored[scored.length - 1].a;
}
+// ---------- DW-network display surface (web ad slots on sister sites) ----------
+// Fill priority: a live PAID ad (billed) → a DW cross-promo → external (stub) → empty.
+let DW_PROMOS = loadJson(path.join(DATA, 'dw-promos.json'), []);
+function pickPromo() {
+ if (!DW_PROMOS.length) return null;
+ const tot = DW_PROMOS.reduce((s, p) => s + (p.weight || 1), 0);
+ let r = Math.random() * tot;
+ for (const p of DW_PROMOS) { r -= (p.weight || 1); if (r <= 0) return p; }
+ return DW_PROMOS[DW_PROMOS.length - 1];
+}
+// external/affiliate fallback hook — returns null until Steve wires a real feed (gated).
+function pickExternalFill() { return null; }
+function pickDisplayFill(context, req) {
+ const paid = pickAd(context);
+ if (paid) {
+ paid.spent_usd = +(paid.spent_usd + paid.cpm_usd / 1000).toFixed(6);
+ paid.impressions = (paid.impressions || 0) + 1;
+ if (paid.spent_usd >= paid.budget_usd) paid.status = 'exhausted';
+ saveAds();
+ logEvent({ type: 'impression', kind: 'display', adId: paid.id, advertiserId: paid.advertiserId, ip: clientIp(req) });
+ return { headline: paid.headline, body: paid.body, sponsor: paid.sponsor, clickUrl: `${PUBLIC_URL}/c/${paid.id}` };
+ }
+ const promo = pickPromo();
+ if (promo) {
+ logEvent({ type: 'impression', kind: 'promo', promoId: promo.id, ip: clientIp(req) });
+ return { headline: promo.headline, body: promo.body, sponsor: promo.sponsor, clickUrl: `${PUBLIC_URL}/cp/${promo.id}` };
+ }
+ return pickExternalFill();
+}
+
const server = http.createServer(async (req, res) => {
const u = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
const p = u.pathname;
@@ -235,6 +265,37 @@ const server = http.createServer(async (req, res) => {
return res.end(`[ad] ${ad.headline} — ${ad.body} → ${clickUrl}\n`);
}
+ // ---------- public: one-line embed script for sister sites ----------
+ if (req.method === 'GET' && p === '/embed.js') {
+ try {
+ const js = fs.readFileSync(path.join(__dirname, 'assets', 'embed.js'), 'utf8');
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8',
+ 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'public, max-age=3600', ...SEC_HEADERS });
+ return res.end(js);
+ } catch (e) { res.writeHead(404, SEC_HEADERS); return res.end('// embed unavailable'); }
+ }
+
+ // ---------- public: serve a DISPLAY ad into a web slot (sister sites) ----------
+ if (req.method === 'GET' && p === '/serve/display') {
+ if (rateLimited(req, res, 'display', 240, 60000)) return;
+ const context = (u.searchParams.get('context') || '').toLowerCase().slice(0, 500).split(/[,\s]+/).filter(Boolean);
+ const fill = pickDisplayFill(context, req);
+ const H = { 'Access-Control-Allow-Origin': '*', ...SEC_HEADERS };
+ if (!fill) { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8', ...H }); return res.end(JSON.stringify({ ad: null })); }
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8', ...H });
+ return res.end(JSON.stringify({ ad: fill }));
+ }
+
+ // ---------- public: DW cross-promo click-through ----------
+ if (req.method === 'GET' && p.startsWith('/cp/')) {
+ if (rateLimited(req, res, 'click', 60, 60000)) return;
+ const promo = DW_PROMOS.find(x => x.id === p.slice(4));
+ const target = promo && validAdUrl(promo.target);
+ if (!target) return json(res, 404, { error: 'unknown promo' });
+ logEvent({ type: 'click', kind: 'promo', promoId: promo.id, ip: clientIp(req) });
+ res.writeHead(302, { Location: target, ...SEC_HEADERS }); return res.end();
+ }
+
// ---------- public: click-through ----------
if (req.method === 'GET' && p.startsWith('/c/')) {
if (rateLimited(req, res, 'click', 60, 60000)) return;
← eb5205f README: public landing surface + domain-ownership note (ads.
·
back to Agent Ad Network
·
Add DW Display Network admin UI (/admin/display): KPIs, live ae1dde9 →