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:
Oliver
2026-06-09 15:04:54 -03:00
parent b51c28758d
commit 3033fc0de2
4 changed files with 320 additions and 0 deletions
+181
View File
@@ -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 &amp; 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 ────────────────────────────────────── */