Add Plausible custom events + weekly insights dashboard + cron
- Custom Plausible events: Pricing Click, Chat Toggle, Chat Message Sent, FAQ Toggle, Speedrun Nav, Mobile Menu Toggle, Scroll Depth (25/50/75/100%) - Weekly Insights section on launch dashboard with visitor/pageview/bounce rate/visit duration/top pages/top referrers + analysis cards - data/insights.json for storing weekly analytics summaries - scripts/check-plausible-email.py — finds latest Plausible email via IMAP - Cron job: every Mon 09:00 PYST, reads Plausible email, writes insights, analyzes and implements improvements
This commit is contained in:
@@ -485,6 +485,91 @@
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
|
||||
|
||||
/* ── Insights Section ───────────────────────────── */
|
||||
.insight-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.insight-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-card);
|
||||
padding: 20px;
|
||||
}
|
||||
.insight-card h3 {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--accent);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.insight-stat {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.1;
|
||||
}
|
||||
.insight-stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.insight-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.insight-list li {
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid rgba(10,48,96,0.3);
|
||||
}
|
||||
.insight-list li:last-child { border-bottom: none; }
|
||||
.insight-list .val { color: var(--accent); font-weight: 600; margin-left: auto; flex-shrink: 0; }
|
||||
.insight-rec {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: var(--radius-card);
|
||||
padding: 16px 20px;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.insight-rec h4 {
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.insight-empty {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
.insight-whatworked { color: var(--success); }
|
||||
.insight-whatdidnt { color: var(--danger); }
|
||||
.insight-neut { color: var(--warning); }
|
||||
.insight-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.insight-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -531,6 +616,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── WEEKLY INSIGHTS ─────────────────────────── -->
|
||||
<div class="section">
|
||||
<div class="section-header" onclick="toggleSection('insights-section', this)">
|
||||
<h2><span class="icon">📈</span> Weekly SEO & Analytics Insights</h2>
|
||||
<span class="section-toggle">▼</span>
|
||||
</div>
|
||||
<div class="section-body" id="insights-section">
|
||||
<div id="insights-content">
|
||||
<div class="insight-empty">Waiting for first analytics report. Data populates weekly from Plausible.</div>
|
||||
</div>
|
||||
<div class="insight-meta" id="insights-meta"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── ACTION PLAN ───────────────────────────── -->
|
||||
<div class="section">
|
||||
<div class="section-header" onclick="toggleSection('actions-section', this)">
|
||||
@@ -924,6 +1023,78 @@
|
||||
return NEEDS;
|
||||
}
|
||||
|
||||
/* ── Render Weekly Insights ─────────────────── */
|
||||
function renderInsights(insights) {
|
||||
const container = document.getElementById("insights-content");
|
||||
const meta = document.getElementById("insights-meta");
|
||||
if (!insights || !insights.week) {
|
||||
container.innerHTML = '<div class="insight-empty">Waiting for first analytics report. Data populates weekly from Plausible.</div>';
|
||||
meta.textContent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const whatWorked = (insights.what_worked || []).map(function (s) {
|
||||
return '<li><span class="insight-whatworked">▲</span> ' + s + '</li>';
|
||||
}).join("");
|
||||
const whatDidnt = (insights.what_didnt || []).map(function (s) {
|
||||
return '<li><span class="insight-whatdidnt">▼</span> ' + s + '</li>';
|
||||
}).join("");
|
||||
const recs = (insights.recommendations || []).map(function (s) {
|
||||
return '<li>→ ' + s + '</li>';
|
||||
}).join("");
|
||||
|
||||
let topPages = "", topRefs = "";
|
||||
if (insights.top_pages && insights.top_pages.length) {
|
||||
topPages = insights.top_pages.map(function (p) {
|
||||
return '<li>' + p.page + ' <span class="val">' + p.visits + '</span></li>';
|
||||
}).join("");
|
||||
}
|
||||
if (insights.top_referrers && insights.top_referrers.length) {
|
||||
topRefs = insights.top_referrers.map(function (r) {
|
||||
return '<li>' + r.source + ' <span class="val">' + r.visits + '</span></li>';
|
||||
}).join("");
|
||||
}
|
||||
|
||||
container.innerHTML = '' +
|
||||
'<div class="insight-grid">' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>👥 Visitors</h3>' +
|
||||
'<div class="insight-stat">' + (insights.visitors || 0) + '</div>' +
|
||||
'<div class="insight-stat-label">' + (insights.visitors_change || '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>📄 Pageviews</h3>' +
|
||||
'<div class="insight-stat">' + (insights.pageviews || 0) + '</div>' +
|
||||
'<div class="insight-stat-label">' + (insights.pageviews_change || '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>🔄 Bounce Rate</h3>' +
|
||||
'<div class="insight-stat">' + (insights.bounce_rate || '—') + '</div>' +
|
||||
'<div class="insight-stat-label">' + (insights.bounce_rate_change || '') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>⏱ Visit Duration</h3>' +
|
||||
'<div class="insight-stat">' + (insights.visit_duration || '—') + '</div>' +
|
||||
'<div class="insight-stat-label">' + (insights.visit_duration_change || '') + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="insight-grid">' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>📑 Top Pages</h3>' +
|
||||
(topPages ? '<ul class="insight-list">' + topPages + '</ul>' : '<div class="insight-empty" style="padding:20px 0">No data yet</div>') +
|
||||
'</div>' +
|
||||
'<div class="insight-card">' +
|
||||
'<h3>🔗 Top Referrers</h3>' +
|
||||
(topRefs ? '<ul class="insight-list">' + topRefs + '</ul>' : '<div class="insight-empty" style="padding:20px 0">No data yet</div>') +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
(whatWorked ? '<div class="insight-rec"><h4>✅ What Worked</h4><ul class="insight-list">' + whatWorked + '</ul></div>' : '') +
|
||||
(whatDidnt ? '<div class="insight-rec"><h4>❌ What Didn\'t</h4><ul class="insight-list">' + whatDidnt + '</ul></div>' : '') +
|
||||
(recs ? '<div class="insight-rec" style="border-left-color:var(--warning)"><h4>💡 Recommendations</h4><ul class="insight-list">' + recs + '</ul></div>' : '');
|
||||
|
||||
meta.textContent = "Updated: " + (insights.updated || "—") + " — Week: " + insights.week;
|
||||
}
|
||||
|
||||
/* ── Toggle sections ─────────────────────────── */
|
||||
function toggleSection(id, header) {
|
||||
const body = document.getElementById(id);
|
||||
@@ -964,6 +1135,16 @@
|
||||
renderKPIs(KPIS);
|
||||
renderActions(ACTIONS);
|
||||
}
|
||||
|
||||
// Also load insights
|
||||
try {
|
||||
const ir = await fetch("data/insights.json?_=" + Date.now());
|
||||
if (ir.ok) {
|
||||
renderInsights(await ir.json());
|
||||
}
|
||||
} catch(e) {
|
||||
// No insights yet — leave placeholder
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Init ────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user