← back to Re Coverage Dashboard
public/index.html
195 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>RE Contact Coverage — liquid progress</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230e1116'/%3E%3Ctext x='8' y='12' font-size='11' text-anchor='middle'%3E%F0%9F%8C%8A%3C/text%3E%3C/svg%3E">
<style>
:root { --bg:#0e1116; --card:#161b22; --line:#232a33; --ink:#e6edf3; --mut:#8b98a6; --acc:#3fb950; --blue:#58a6ff; }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--ink); font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; }
header { padding:20px 24px 8px; }
h1 { font-size:20px; margin:0; font-weight:650; letter-spacing:.2px; }
.sub { color:var(--mut); font-size:12px; margin-top:3px; }
.totals { display:flex; gap:22px; margin-top:12px; flex-wrap:wrap; }
.tot { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:8px 14px; }
.tot b { font-size:19px; color:var(--acc); } .tot .l { font-size:11px; color:var(--mut); display:block; }
.grp { padding:6px 24px 0; }
.grp h2 { font-size:12px; text-transform:uppercase; letter-spacing:1px; color:var(--mut); margin:16px 0 4px; }
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:14px; }
.card { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:14px; text-align:center; }
.card canvas { width:130px; height:130px; }
.card .lab { font-size:12.5px; font-weight:600; margin-top:6px; }
.card .cnt { font-size:11px; color:var(--mut); margin-top:2px; }
.wins { padding:8px 24px 30px; }
.wins h2 { font-size:12px; text-transform:uppercase; letter-spacing:1px; color:var(--mut); margin:18px 0 8px; }
.win { display:flex; gap:10px; align-items:center; background:var(--card); border:1px solid var(--line); border-left:3px solid var(--acc); border-radius:8px; padding:8px 12px; margin-bottom:6px; }
.win .t { flex:1; } .win .when { font-size:11px; color:var(--mut); }
.empty { color:var(--mut); font-size:13px; padding:8px 0; }
</style>
<link rel="stylesheet" href="/nav-agent/nav-agent.css"><!-- nav-agent -->
<script src="/drill.js"></script>
</head>
<body>
<input type="hidden" id="q" value=""><input type="hidden" id="pattern" value="">
<header>
<h1>🌊 RE Contact Coverage</h1>
<div class="sub">Filling in phone · firm · site · agent-site across usre + CRCP — $0/local discovery. <span id="upd"></span></div>
<div class="totals" id="totals"></div>
</header>
<div id="groups"></div>
<div class="wins"><h2>🏆 Wins / milestones</h2><div id="winlist"><div class="empty">no milestones yet — they appear as coverage crosses thresholds</div></div></div>
<script>
// href-drill primitives (TK-10903)
var FIELDS=[['group','Group']];
var FIL={group:''};
var $=function(s){return document.querySelector(s);};
function applyFilters(push){if(push)writeURL(true);built=false;refresh();}
// XSS-safe HTML escaper — used on any server-sourced string interpolated into innerHTML.
function esc(s) {
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
}
// ── Liquid-fill canvas gauge: a clipped circle with an animated sine-wave surface. ──
class LiquidGauge {
constructor(canvas, color) {
this.c = canvas; this.ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1; this.s = 130;
canvas.width = this.s * dpr; canvas.height = this.s * dpr; this.ctx.scale(dpr, dpr);
this.color = color; this.level = 0; this.target = 0; this.phase = 0;
requestAnimationFrame(() => this.draw());
}
setTarget(pct) { this.target = Math.max(0, Math.min(100, pct)); }
draw() {
const { ctx, s } = this, r = s / 2 - 6, cx = s / 2, cy = s / 2;
this.level += (this.target - this.level) * 0.06; // ease toward target
this.phase += 0.045;
ctx.clearRect(0, 0, s, s);
// ring
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255,255,255,.10)'; ctx.lineWidth = 3; ctx.stroke();
// water clipped to circle
ctx.save();
ctx.beginPath(); ctx.arc(cx, cy, r - 1, 0, Math.PI * 2); ctx.clip();
const waterY = cy + r - (this.level / 100) * (2 * r);
const amp = 4 + (this.level > 1 && this.level < 99 ? 2 : 0);
for (let k = 0; k < 2; k++) {
ctx.beginPath();
ctx.moveTo(cx - r, s);
for (let x = -r; x <= r; x += 4) {
const y = waterY + Math.sin((x / r) * Math.PI * 2 + this.phase + k * Math.PI) * amp;
ctx.lineTo(cx + x, y);
}
ctx.lineTo(cx + r, s); ctx.closePath();
ctx.fillStyle = k === 0 ? this.color : this.color + '99';
ctx.fill();
}
ctx.restore();
// pct text
ctx.fillStyle = '#e6edf3'; ctx.font = '700 26px -apple-system,sans-serif';
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillText(this.level.toFixed(this.target < 10 ? 1 : 0) + '%', cx, cy);
requestAnimationFrame(() => this.draw());
}
}
const COLORS = { usre: '#3fb950', CRCP: '#58a6ff', HomesOnSpec: '#d29922' };
const gauges = {};
var built = false; // var so applyFilters (drill) can reset it
function build(metrics) {
const filtered = FIL.group ? metrics.filter(m=>m.group===FIL.group) : metrics;
const groups = {};
filtered.forEach(m => { (groups[m.group] = groups[m.group] || []).push(m); });
const host = document.getElementById('groups'); host.innerHTML = '';
for (const [g, ms] of Object.entries(groups)) {
const sec = document.createElement('div'); sec.className = 'grp';
sec.innerHTML = `<h2>${drill('group',g)}</h2><div class="grid"></div>`;
const grid = sec.querySelector('.grid');
ms.forEach(m => {
const card = document.createElement('div'); card.className = 'card';
card.innerHTML = `<canvas id="g_${esc(m.key)}"></canvas><div class="lab">${esc(m.label)}</div><div class="cnt" id="c_${esc(m.key)}"></div>`;
grid.appendChild(card);
});
host.appendChild(sec);
ms.forEach(m => { gauges[m.key] = new LiquidGauge(document.getElementById('g_' + m.key), COLORS[g] || '#3fb950'); });
}
built = true;
}
async function refresh() {
const d = await fetch('/api/coverage').then(r => r.json()).catch(() => null);
if (!d) return;
if (!built) build(d.metrics);
d.metrics.forEach(m => {
gauges[m.key] && gauges[m.key].setTarget(m.pct);
const c = document.getElementById('c_' + m.key);
if (c) c.innerHTML = `<a href="/api/coverage" style="color:inherit;text-decoration:none">${(m.num || 0).toLocaleString()} / ${(m.den || 0).toLocaleString()} ${esc(m.unit || '')}</a>`;
});
const t = d.totals;
document.getElementById('totals').innerHTML = [
['Sites found', t.sites_found], ['Phones found', t.phones_found],
['Agents searched', t.searched], ['Firm-linked', t.firm_linked],
].map(([l, v]) => `<div class="tot"><a href="/api/coverage" style="color:inherit;text-decoration:none"><b>${(v || 0).toLocaleString()}</b></a><span class="l">${l}</span></div>`).join('');
document.getElementById('upd').textContent = 'updated ' + new Date(d.updated).toLocaleTimeString();
const w = await fetch('/api/wins').then(r => r.json()).catch(() => ({ wins: [] }));
const wl = document.getElementById('winlist');
if (w.wins && w.wins.length) wl.innerHTML = w.wins.map(x =>
`<div class="win"><span>🏆</span><span class="t">${esc(x.title)}</span><span class="when">${new Date(x.ts).toLocaleString()}</span></div>`).join('');
}
readURL();
refresh(); setInterval(refresh, 15000);
</script>
<!-- Own GA4 + FB Pixel (DW build standard). Ids come from server-injected window.__A
(GA4_ID / FB_PIXEL_ID env). Both no-op until their id is set — no tracking fires blank. -->
<script>
(function(){ var A=window.__A||{};
if (A.ga4) { var g=document.createElement('script'); g.async=true;
g.src='https://www.googletagmanager.com/gtag/js?id='+A.ga4; document.head.appendChild(g);
window.dataLayer=window.dataLayer||[]; function gtag(){dataLayer.push(arguments);}
window.gtag=gtag; gtag('js', new Date()); gtag('config', A.ga4); }
if (A.fb) { !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;
n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}
(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', A.fb); fbq('track', 'PageView'); }
})();
</script>
<!-- DW-network ad slot (per the DW build standard): renders a "Sponsored" unit;
every ad link is rel="nofollow sponsored noopener". Fail-open — no fill = nothing. -->
<script src="https://ads.agentabrams.com/embed.js"
data-slot="footer-strip"
data-site="coverage.agentabrams.com"
data-context="real-estate,analytics,agents,brokers"></script>
<script src="/nav-agent/nav-agent.js" defer></script><!-- nav-agent -->
</body>
</html>