Admin traffic: conversion columns (visit to signup, signup to registered, signup to buyer), stacked centered headers

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-13 07:03:04 -05:00
parent 4efba20d4e
commit 0ad974ca59
2 changed files with 9 additions and 4 deletions
+3 -1
View File
@@ -10,6 +10,8 @@
<style> <style>
.adm-table{width:100%;table-layout:auto} .adm-table{width:100%;table-layout:auto}
.adm-table th{cursor:pointer;user-select:none;white-space:nowrap} .adm-table th{cursor:pointer;user-select:none;white-space:nowrap}
#trfSources th,#trfAngles th{white-space:normal;text-align:center;line-height:1.2;vertical-align:bottom}
#trfSources td:not(:first-child),#trfAngles td:not(:first-child){text-align:center;font-variant-numeric:tabular-nums}
.adm-table th.sort-asc::after{content:' \25B2';font-size:9px;color:var(--mint)} .adm-table th.sort-asc::after{content:' \25B2';font-size:9px;color:var(--mint)}
.adm-table th.sort-desc::after{content:' \25BC';font-size:9px;color:var(--mint)} .adm-table th.sort-desc::after{content:' \25BC';font-size:9px;color:var(--mint)}
.adm-table.kv th{cursor:default;color:var(--muted);font-weight:500} .adm-table.kv th{cursor:default;color:var(--muted);font-weight:500}
@@ -421,6 +423,6 @@
</div> </div>
<script src="/assets/common.js?v=20260913a"></script> <script src="/assets/common.js?v=20260913a"></script>
<script src="/assets/admin.js?v=20260913b"></script> <script src="/assets/admin.js?v=20260913c"></script>
</body> </body>
</html> </html>
+6 -3
View File
@@ -461,10 +461,13 @@
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c])); const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
const n = v => Number(v || 0).toLocaleString(); const n = v => Number(v || 0).toLocaleString();
$('trfSub').textContent = 'last ' + d.days + ' days · ' + n(d.totals.hits) + ' page views · ' + n(d.totals.joinViews) + ' join-page views · ' + n(d.totals.signups) + ' signups · ' + n(d.totals.buyers) + ' buyers'; $('trfSub').textContent = 'last ' + d.days + ' days · ' + n(d.totals.hits) + ' page views · ' + n(d.totals.joinViews) + ' join-page views · ' + n(d.totals.signups) + ' signups · ' + n(d.totals.buyers) + ' buyers';
$('trfSources').innerHTML = '<tr><th>Source</th><th>Page views</th><th>Join-page views</th><th>Signups</th><th>Registered</th><th>$20+ buyers</th></tr>' // conversion columns (Marty, 2026-09-13): visits = page views + join-page views; signup rate is per visit,
+ (d.sources.length ? d.sources.map(s => '<tr><td>' + esc(s.source) + '</td><td>' + n(s.hits) + '</td><td>' + n(s.joinViews) + '</td><td>' + n(s.signups) + '</td><td>' + n(s.registered) + '</td><td>' + n(s.buyers) + '</td></tr>').join('') : '<tr><td colspan="6" class="muted">Nothing recorded in this range yet.</td></tr>'); // registered and buyer rates are per signup (what happened to the people who did sign up)
const pct = (num, den) => den ? (100 * num / den).toFixed(num && 100 * num / den < 10 ? 1 : 0) + '%' : '<span class="muted">-</span>';
$('trfSources').innerHTML = '<tr><th>Source</th><th>Page<br>views</th><th>Join-page<br>views</th><th>Signups</th><th>Visit &rarr;<br>signup</th><th>Registered</th><th>Signup &rarr;<br>registered</th><th>$20+<br>buyers</th><th>Signup &rarr;<br>buyer</th></tr>'
+ (d.sources.length ? d.sources.map(s => '<tr><td>' + esc(s.source) + '</td><td>' + n(s.hits) + '</td><td>' + n(s.joinViews) + '</td><td>' + n(s.signups) + '</td><td>' + pct(s.signups, s.hits + s.joinViews) + '</td><td>' + n(s.registered) + '</td><td>' + pct(s.registered, s.signups) + '</td><td>' + n(s.buyers) + '</td><td>' + pct(s.buyers, s.signups) + '</td></tr>').join('') : '<tr><td colspan="9" class="muted">Nothing recorded in this range yet.</td></tr>');
$('trfPaths').innerHTML = '<tr><th>Page</th><th>Views</th></tr>' + (d.paths.length ? d.paths.map(p => '<tr><td>' + esc(p.path) + '</td><td>' + n(p.hits) + '</td></tr>').join('') : '<tr><td colspan="2" class="muted">No page views yet.</td></tr>'); $('trfPaths').innerHTML = '<tr><th>Page</th><th>Views</th></tr>' + (d.paths.length ? d.paths.map(p => '<tr><td>' + esc(p.path) + '</td><td>' + n(p.hits) + '</td></tr>').join('') : '<tr><td colspan="2" class="muted">No page views yet.</td></tr>');
$('trfAngles').innerHTML = '<tr><th>Angle</th><th>Join-page views</th><th>Signups</th></tr>' + (d.angles.length ? d.angles.map(a => '<tr><td>' + esc(a.angle) + '</td><td>' + n(a.views) + '</td><td>' + n(a.signups) + '</td></tr>').join('') : '<tr><td colspan="3" class="muted">No angle data yet.</td></tr>'); $('trfAngles').innerHTML = '<tr><th>Angle</th><th>Join-page<br>views</th><th>Signups</th><th>View &rarr;<br>signup</th></tr>' + (d.angles.length ? d.angles.map(a => '<tr><td>' + esc(a.angle) + '</td><td>' + n(a.views) + '</td><td>' + n(a.signups) + '</td><td>' + pct(a.signups, a.views) + '</td></tr>').join('') : '<tr><td colspan="4" class="muted">No angle data yet.</td></tr>');
$('trfDaily').innerHTML = '<tr><th>Day</th><th>Page views</th><th>Signups</th></tr>' + (d.daily.length ? d.daily.slice().reverse().map(x => '<tr><td>' + esc(x.day) + '</td><td>' + n(x.hits) + '</td><td>' + n(x.signups) + '</td></tr>').join('') : '<tr><td colspan="3" class="muted">Nothing yet.</td></tr>'); $('trfDaily').innerHTML = '<tr><th>Day</th><th>Page views</th><th>Signups</th></tr>' + (d.daily.length ? d.daily.slice().reverse().map(x => '<tr><td>' + esc(x.day) + '</td><td>' + n(x.hits) + '</td><td>' + n(x.signups) + '</td></tr>').join('') : '<tr><td colspan="3" class="muted">Nothing yet.</td></tr>');
} }
// ── blog: coaching articles, public at /blog (Marty, 2026-09-12) ── // ── blog: coaching articles, public at /blog (Marty, 2026-09-12) ──