← back to Re Flyer Aggregator
public/advertising/index.html
102 lines
<!doctype html><html lang="en"><head>
<script>
// Credential-safe fetch guard (fleet drop-in) — if this page is opened with
// credentials in the URL (a saved bookmark / Chrome-remembered basic-auth:
// https://user:pass@host/…), the browser poisons document.baseURI, so any
// relative or root-relative fetch('/api/…') throws "Request cannot be constructed
// from a URL that includes credentials" and callers silently fall to an empty
// state. Resolve every non-absolute request URL against the credential-free
// location instead of baseURI. Placed first so it wraps window.fetch before any
// app script runs. Ref: creds-in-url-fetch-guard-fleet-pattern.
(function () {
var _fetch = window.fetch.bind(window);
var cleanBase = function () { return location.origin + location.pathname; };
window.fetch = function (input, init) {
try {
if (typeof input === 'string' && !/^[a-z]+:\/\//i.test(input) && input.indexOf('//') !== 0) {
input = new URL(input, cleanBase()).href;
} else if (input instanceof Request && !/^[a-z]+:\/\//i.test(input.url)) {
input = new Request(new URL(input.url, cleanBase()).href, input);
}
} catch (_) { /* fall through to native */ }
return _fetch(input, init);
};
})();
</script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Broker Advertising Intel — CRCP</title>
<style>
:root{--navy:#0E2350;--blue:#16357A;--gold:#E8A81C;--ink:#141414;--gray:#6B7280}
*{box-sizing:border-box} body{margin:0;font-family:Inter,-apple-system,Helvetica,Arial,sans-serif;background:#f4f6fb;color:var(--ink)}
header{background:var(--navy);color:#fff;padding:18px 26px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px}
header h1{margin:0;font-size:20px} header .s{color:var(--gold);font-size:12px}
.dl{background:var(--gold);color:#111;font-weight:700;font-size:13px;padding:8px 14px;border-radius:7px;text-decoration:none}
.controls{position:sticky;top:0;z-index:5;background:#fff;border-bottom:1px solid #e6e9f2;padding:12px 26px;display:flex;gap:14px;align-items:center;flex-wrap:wrap;font-size:13px}
.controls input,.controls select{padding:6px 9px;border:1px solid #d6dced;border-radius:6px;font-size:13px}
.stat{color:var(--gray);margin-left:auto;font-variant-numeric:tabular-nums}
table{border-collapse:collapse;width:100%;font-size:13px}
thead th{position:sticky;top:52px;background:#eef1f8;text-align:left;padding:9px 12px;color:#445;cursor:pointer;white-space:nowrap;border-bottom:2px solid #dde3f0}
td{padding:8px 12px;border-bottom:1px solid #eef0f6;vertical-align:top}
tr:hover td{background:#fbfcff}
a{color:var(--blue);text-decoration:none} a:hover{text-decoration:underline}
.chip{display:inline-block;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:4px;margin:1px 2px 1px 0}
.g{background:#0E7C4A} .b{background:#0b6fb8} .m{background:#1877F2} .l{background:#0a66c2} .t{background:#111} .x{background:#8A5A00}
.none{color:#c3c9d6} .flyer{color:#8A5A00;font-weight:600}
.paid{font-weight:800;color:#0E7C4A;text-align:center} .no{color:#c3c9d6;text-align:center}
</style></head><body>
<header>
<div><h1>Broker Advertising Intel</h1><div class="s">CRCP · who's running ads + flyers found on their sites · from page code</div></div>
<a class="dl" href="broker-ad-signals.csv" download>⬇ Download CSV</a>
</header>
<div class="controls">
<input id="q" placeholder="search firm / domain…" size="22">
<select id="filt">
<option value="">All brokers</option>
<option value="paid">Runs ANY paid ads</option>
<option value="google_ads">Google Ads</option>
<option value="bing_ads">Bing Ads</option>
<option value="meta">Meta</option>
<option value="linkedin">LinkedIn</option>
<option value="flyers">Has flyers/PDFs on site</option>
</select>
<div class="stat" id="stat"></div>
</div>
<table><thead><tr>
<th data-k="firm">Firm</th><th data-k="domain">Domain</th><th data-k="paid_count">Paid ads</th>
<th data-k="_plats">Platforms</th><th data-k="google_analytics">GA</th><th data-k="flyer_count">Flyers/PDFs</th><th data-k="scanned_at">Scanned</th>
</tr></thead><tbody id="tb"></tbody></table>
<script>
const PLAT={google_ads:['g','Google'],bing_ads:['b','Bing'],meta:['m','Meta'],linkedin:['l','LinkedIn'],tiktok:['t','TikTok'],twitter:['t','X'],pinterest:['x','Pinterest'],reddit:['x','Reddit'],snap:['x','Snap'],amazon:['x','Amazon'],criteo:['x','Criteo'],taboola:['x','Taboola'],outbrain:['x','Outbrain'],adroll:['x','AdRoll'],doubleclick:['x','DoubleClick']};
let DATA=[],sortK='paid_count',sortDir=-1;
const fmtWhen=s=>{if(!s)return '<span class="none">—</span>';const d=new Date(s);return isNaN(d)?'—':d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'});};
const plats=r=>Object.keys(PLAT).filter(p=>r[p]);
function chips(r){const on=plats(r);return on.length?on.map(p=>`<span class="chip ${PLAT[p][0]}">${PLAT[p][1]}</span>`).join(''):'<span class="none">—</span>';}
function flyerCell(r){if(!r.flyer_count)return '<span class="none">—</span>';
return `<span class="flyer">${r.flyer_count}</span> ` + (r.flyer_urls||[]).slice(0,3).map((u,i)=>`<a href="${u}" target="_blank" rel="noopener noreferrer">pdf${i+1}↗</a>`).join(' ');}
function render(){
const q=document.getElementById('q').value.toLowerCase(),f=document.getElementById('filt').value;
let rows=DATA.filter(r=>!r.error);
if(q)rows=rows.filter(r=>((r.firm||'')+' '+r.domain).toLowerCase().includes(q));
if(f==='paid')rows=rows.filter(r=>r.paid_count>0);
else if(f==='flyers')rows=rows.filter(r=>r.flyer_count>0);
else if(f)rows=rows.filter(r=>r[f]);
rows.sort((a,b)=>{let x=a[sortK],y=b[sortK];if(sortK==='firm'||sortK==='domain'||sortK==='scanned_at'){return (''+(x||'')).localeCompare(''+(y||''))*sortDir;}return ((x||0)-(y||0))*sortDir;});
document.getElementById('tb').innerHTML=rows.map(r=>`<tr>
<td><b>${(r.firm||'').replace(/</g,'<')||'—'}</b></td>
<td><a href="https://${r.domain}" target="_blank" rel="noopener noreferrer">${r.domain}</a></td>
<td class="${r.paid_count?'paid':'no'}">${r.paid_count||'·'}</td>
<td>${chips(r)}</td>
<td style="text-align:center">${r.google_analytics?'✓':'<span class="none">·</span>'}</td>
<td>${flyerCell(r)}</td>
<td style="color:#8A5A00;white-space:nowrap" title="${r.scanned_at||''}">${fmtWhen(r.scanned_at)}</td></tr>`).join('');
const adv=rows.filter(r=>r.paid_count>0).length,fl=rows.filter(r=>r.flyer_count>0).length;
const errored=DATA.filter(r=>r.error).length, gtm=rows.filter(r=>(r.via_gtm||[]).length).length;
document.getElementById('stat').textContent=`${rows.length} scanned · ${adv} advertising${gtm?` (${gtm} via GTM)`:''} · ${fl} with flyers${errored?` · ${errored} unreachable`:''}`;
}
document.querySelectorAll('th').forEach(th=>th.onclick=()=>{const k=th.dataset.k;if(k==='_plats')return;if(sortK===k)sortDir*=-1;else{sortK=k;sortDir=(k==='firm'||k==='domain')?1:-1;}render();});
document.getElementById('q').oninput=render;document.getElementById('filt').onchange=render;
fetch('broker-ad-signals.json?t='+Date.now()).then(r=>r.json()).then(j=>{DATA=j;render();}).catch(e=>document.getElementById('stat').textContent='load error: '+e.message);
setInterval(()=>fetch('broker-ad-signals.json?t='+Date.now()).then(r=>r.json()).then(j=>{DATA=j;render();}).catch(()=>{}),15000);
</script></body></html>