Files
derez.ai/operations.html
T

1032 lines
42 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>Daily Brief — Derez.ai</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #08080c;
--bg-card: #0e0e14;
--bg-inner: #0a0a0f;
--bg-hover: #16161f;
--border: #1e1e2a;
--border-hi: #2a2a3a;
--accent: #6c8cff;
--accent-dim: #1a1a3a;
--danger: #ef4444;
--danger-dim: #2a1018;
--success: #22c55e;
--success-dim: #0a1f12;
--warning: #f59e0b;
--warning-dim: #1f1508;
--text: #e8e8f0;
--text-muted: #787898;
--radius: 8px;
--radius-card: 14px;
--max-width: 960px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
font-size: 14px;
line-height: 1.7;
padding: 40px 0;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
}
code {
font-family: "JetBrains Mono", "Fira Code", monospace;
font-size: .875em;
}
/* ── Header ──────────────────────────── */
.header {
margin-bottom: 32px;
}
.header .date {
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
letter-spacing: .02em;
margin-bottom: 4px;
}
.header h1 {
font-size: 22px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.header .sub {
font-size: 13px;
color: var(--text-muted);
}
.badge-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 12px;
border-radius: 999px;
font-size: 11px;
font-weight: 500;
}
.badge.live { background: var(--success-dim); color: var(--success); border: 1px solid rgba(34,197,94,.2); }
.badge.warn { background: var(--warning-dim); color: var(--warning); border: 1px solid rgba(245,158,11,.2); }
.badge.info { background: var(--accent-dim); color: var(--accent); border: 1px solid rgba(108,140,255,.2); }
.updated { font-size: 11px; color: var(--text-muted); margin-top: 10px; }
/* ── Section ─────────────────────────── */
.section { margin-bottom: 36px; }
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 0;
cursor: pointer;
user-select: none;
padding: 6px 0;
}
.section-header:hover { color: var(--accent); }
.section-header h2 {
font-size: 15px;
font-weight: 600;
color: var(--text);
}
.section-header:hover h2 { color: var(--accent); }
.section-header .count {
font-size: 12px;
color: var(--text-muted);
font-weight: 400;
}
.section-toggle {
color: var(--text-muted);
font-size: 14px;
margin-left: auto;
transition: transform .2s;
}
.section-toggle.collapsed { transform: rotate(-90deg); }
.section-body {
transition: max-height .25s ease, opacity .15s ease, margin .15s ease;
overflow: hidden;
max-height: 0;
opacity: 0;
margin: 0;
}
.section-body.open {
max-height: 20000px;
opacity: 1;
margin-top: 12px;
}
/* ── Goals Grid ──────────────────────── */
.goals-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.goal-card {
background: var(--bg-card);
border-radius: var(--radius-card);
padding: 18px;
}
.goal-card .goal-label {
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--text-muted);
margin-bottom: 4px;
}
.goal-card .goal-current {
font-size: 26px;
font-weight: 700;
color: var(--text);
line-height: 1.2;
}
.goal-card .goal-target {
font-size: 14px;
color: var(--text-muted);
font-weight: 500;
}
.goal-card .goal-bar {
margin-top: 10px;
height: 4px;
background: var(--bg-inner);
border-radius: 2px;
overflow: hidden;
}
.goal-card .goal-bar-fill {
height: 100%;
border-radius: 2px;
transition: width .6s ease;
}
.goal-card .goal-bar-fill.green { background: var(--success); }
.goal-card .goal-bar-fill.blue { background: var(--accent); }
.goal-card .goal-bar-fill.yellow { background: var(--warning); }
.goal-card .goal-bar-fill.red { background: var(--danger); }
.goal-card .goal-sub {
font-size: 11px;
color: var(--text-muted);
margin-top: 4px;
}
/* ── Campaign Cards ──────────────────── */
.campaign-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.campaign-card {
background: var(--bg-card);
border-radius: var(--radius-card);
padding: 18px;
}
.campaign-card h3 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--accent);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 6px;
}
.campaign-card .status-tag {
display: inline-block;
font-size: 10px;
padding: 2px 7px;
border-radius: 4px;
font-weight: 500;
}
.campaign-card .status-tag.active { background: var(--success-dim); color: var(--success); border: 1px solid rgba(34,197,94,.2); }
.campaign-card .status-tag.paused { background: var(--warning-dim); color: var(--warning); border: 1px solid rgba(245,158,11,.2); }
.campaign-card .status-tag.draft { background: rgba(120,120,152,.08); color: var(--text-muted); border: 1px solid transparent; }
.campaign-card .stat-row {
display: flex;
gap: 12px;
margin-bottom: 8px;
}
.campaign-card .stat-item {
text-align: center;
flex: 1;
}
.campaign-card .stat-num {
font-size: 20px;
font-weight: 700;
color: var(--text);
}
.campaign-card .stat-label {
font-size: 9px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .02em;
}
.campaign-card ul { list-style: none; margin-top: 6px; }
.campaign-card li {
font-size: 12px;
padding: 4px 0;
border-bottom: 1px solid rgba(30,30,42,.4);
display: flex;
align-items: center;
gap: 6px;
}
.campaign-card li:last-child { border-bottom: none; }
.campaign-card li .dot {
width: 6px; height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
.dot.green { background: var(--success); }
.dot.red { background: var(--danger); }
.dot.yellow { background: var(--warning); }
/* ── Needs (checkboxes) ──────────────── */
.needs-card {
background: var(--bg-card);
border-radius: var(--radius-card);
padding: 20px 24px;
}
.needs-list { list-style: none; }
.needs-list li {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid rgba(30,30,42,.4);
}
.needs-list li:last-child { border-bottom: none; }
.need-checkbox {
width: 20px;
height: 20px;
min-width: 20px;
border: 2px solid var(--border-hi);
border-radius: 5px;
flex-shrink: 0;
margin-top: 2px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all .15s;
}
.need-checkbox:hover { border-color: var(--accent); }
.need-checkbox.checked {
background: var(--success);
border-color: var(--success);
}
.need-checkbox.checked::after {
content: "✓";
color: #08080c;
font-size: 14px;
font-weight: 700;
}
.need-item.done .need-text {
color: var(--text-muted);
text-decoration: line-through;
}
.need-text {
font-size: 14px;
color: var(--text);
line-height: 1.6;
}
/* ── Comms ───────────────────────────── */
.comms-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.comms-card {
background: var(--bg-card);
border-radius: var(--radius-card);
padding: 18px;
}
.comms-card h3 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--accent);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 6px;
}
.comms-card .comms-summary {
display: flex;
gap: 16px;
margin-bottom: 10px;
}
.comms-card .comms-stat { text-align: center; }
.comms-card .comms-stat-num {
font-size: 20px;
font-weight: 700;
color: var(--text);
}
.comms-card .comms-stat-label {
font-size: 9px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .02em;
}
.comms-list { list-style: none; }
.comms-list li {
font-size: 12px;
padding: 5px 0;
border-bottom: 1px solid rgba(30,30,42,.4);
display: flex;
align-items: flex-start;
gap: 8px;
line-height: 1.5;
}
.comms-list li:last-child { border-bottom: none; }
.comms-list .comms-time {
color: var(--text-muted);
font-size: 11px;
white-space: nowrap;
flex-shrink: 0;
min-width: 48px;
}
.comms-list .comms-subj { color: var(--text); }
.comms-list .comms-detail {
color: var(--text-muted);
font-size: 11px;
}
.comms-list .comms-from {
color: var(--accent);
font-size: 11px;
}
.comms-alert {
background: var(--danger-dim);
border: 1px solid rgba(239,68,68,.2);
border-left: 3px solid var(--danger);
border-radius: var(--radius);
padding: 10px 14px;
margin-top: 8px;
font-size: 12px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.comms-alert .alert-icon { color: var(--danger); flex-shrink: 0; font-size: 14px; }
.comms-alert .alert-body { color: var(--text); }
.comms-alert .alert-detail { color: var(--text-muted); font-size: 11px; margin-top: 2px; }
/* ── Daily routine ───────────────────── */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.info-card {
background: var(--bg-card);
border-radius: var(--radius-card);
padding: 20px;
}
.info-card h3 {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--accent);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.cron-timeline {
position: relative;
padding-left: 24px;
}
.cron-timeline::before {
content: "";
position: absolute;
left: 7px;
top: 4px;
bottom: 4px;
width: 2px;
background: var(--border);
}
.cron-item {
position: relative;
padding: 4px 0 14px;
}
.cron-item::before {
content: "";
position: absolute;
left: -22px;
top: 8px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--bg-card);
border: 2px solid var(--accent);
}
.cron-item.primary::before {
background: var(--accent);
box-shadow: 0 0 8px rgba(108,140,255,.4);
}
.cron-time {
font-size: 11px;
color: var(--accent);
font-weight: 600;
}
.cron-title {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.cron-desc {
font-size: 12px;
color: var(--text-muted);
margin-top: 1px;
line-height: 1.5;
}
.highlight { color: var(--accent); }
/* ── Infos ───────────────────────────── */
.infos-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 20px 24px;
}
.infos-list { list-style: none; }
.infos-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 7px 0;
border-bottom: 1px solid rgba(30,30,42,.4);
font-size: 13px;
color: var(--text);
line-height: 1.6;
}
.infos-list li:last-child { border-bottom: none; }
.infos-list li .info-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--accent);
white-space: nowrap;
flex-shrink: 0;
min-width: 100px;
}
.infos-list li .info-value {
color: var(--text);
}
@media (max-width: 900px) {
.campaign-grid { grid-template-columns: 1fr; }
.comms-grid { grid-template-columns: 1fr; }
.goals-grid { grid-template-columns: repeat(2, 1fr); }
.grid-2 { grid-template-columns: 1fr; }
}
@media (max-width: 500px) {
.goals-grid { grid-template-columns: 1fr; }
}
/* ── Blog ideas ──────────────────────────── */
.blog-idea-item {
display: flex;
gap: 16px;
padding: 20px 24px;
background: #0e0e14;
border: 1px solid #1a1a2e;
border-radius: 18px;
transition: border-color 0.2s, transform 0.2s;
align-items: flex-start;
margin-bottom: 16px;
}
.blog-idea-item:hover {
border-color: #6c8cff;
transform: translateY(-2px);
}
.blog-idea-checkbox {
flex-shrink: 0;
padding-top: 3px;
}
.blog-idea-checkbox input[type="checkbox"] {
accent-color: #6c8cff;
width: 17px;
height: 17px;
cursor: default;
}
.blog-idea-body { flex: 1; min-width: 0; }
.blog-idea-title {
font-weight: 700;
font-size: 16px;
color: #e8e8f0;
margin-bottom: 6px;
}
.blog-idea-desc {
font-size: 13px;
color: #888;
line-height: 1.7;
margin-bottom: 10px;
}
.blog-idea-meta {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.blog-idea-tag {
display: inline-block;
padding: 3px 12px;
font-size: 11px;
font-weight: 600;
border-radius: 12px;
background: rgba(108,140,255,0.1);
color: #6c8cff;
border: 1px solid rgba(108,140,255,0.15);
letter-spacing: 0.02em;
}
</style>
</head>
<body>
<div class="container">
<!-- ════════ HEADER ════════ -->
<div class="header">
<div class="date" id="brief-date">Loading...</div>
<h1>Daily Brief &mdash; Q2 2026</h1>
<div class="badge-row">
<span class="badge live">● Site Live</span>
<span class="badge warn">● Prelaunch</span>
<span class="badge info">◉ 89 CRM Contacts</span>
</div>
<div class="updated" id="brief-updated">Last updated: &mdash;</div>
</div>
<!-- ════════ GOALS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('goals-section', this)">
<h2>🎯 Q2/2026 Goals</h2>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="goals-section">
<div class="goals-grid" id="goals-grid"></div>
</div>
</div>
<!-- ════════ CAMPAIGNS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('campaigns-section', this)">
<h2>📡 Campaigns</h2>
<span class="section-count count">overview</span>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="campaigns-section">
<div class="campaign-grid" id="campaign-grid"></div>
</div>
</div>
<!-- ════════ NEEDS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('needs-section', this)">
<h2>📦 Items Needed From You</h2>
<span class="section-count count">click to check off</span>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="needs-section">
<div class="needs-card">
<ul class="needs-list" id="needs-list"></ul>
</div>
</div>
</div>
<!-- ════════ COMMS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('comms-section', this)">
<h2>✉ Communications</h2>
<span class="section-count count">important only</span>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="comms-section">
<div class="comms-grid" id="comms-grid"></div>
</div>
</div>
<!-- ════════ INFOS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('infos-section', this)">
<h2>📋 Infos</h2>
<span class="section-count count">look here for answers</span>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="infos-section">
<div class="infos-card">
<ul class="infos-list" id="infos-list"></ul>
</div>
</div>
</div>
<!-- ════════ ROUTINE ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('routine-section', this)">
<h2>⏰ Daily Routine</h2>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="routine-section">
<div class="grid-2">
<div class="info-card">
<h3>⏱ 1-Hour Block &mdash; 09:00 PYST</h3>
<div class="cron-timeline">
<div class="cron-item primary">
<div class="cron-time">09:00 &ndash; 09:05</div>
<div class="cron-title">Dashboard review</div>
<div class="cron-desc">Review KPIs &amp; overnight changes</div>
</div>
<div class="cron-item primary">
<div class="cron-time">09:05 &ndash; 09:15</div>
<div class="cron-title">Status sync</div>
<div class="cron-desc">Check needs, unblock items</div>
</div>
<div class="cron-item primary">
<div class="cron-time">09:15 &ndash; 09:45</div>
<div class="cron-title">Deep work</div>
<div class="cron-desc">Today's priorities &mdash; outreach, content, DMs</div>
</div>
<div class="cron-item primary">
<div class="cron-time">09:45 &ndash; 10:00</div>
<div class="cron-title">Wrap &amp; queue</div>
<div class="cron-desc">Set tomorrow's priorities, log progress</div>
</div>
</div>
</div>
<div class="info-card">
<h3>🤖 Cron Jobs</h3>
<div class="cron-timeline">
<div class="cron-item">
<div class="cron-time">03:00</div>
<div class="cron-title">📊 Dashboard update</div>
<div class="cron-desc">KPI snapshot to <code>data/operations.json</code></div>
</div>
<div class="cron-item">
<div class="cron-time">03:10</div>
<div class="cron-title">📈 SEO tracker</div>
<div class="cron-desc">Search Console impressions, keyword positions</div>
</div>
<div class="cron-item">
<div class="cron-time">03:20</div>
<div class="cron-title">🔗 Backlink monitor</div>
<div class="cron-desc">New referring domains flagged</div>
</div>
<div class="cron-item">
<div class="cron-time">03:30</div>
<div class="cron-title">💬 Social monitor</div>
<div class="cron-desc">Brand mentions &amp; replies</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ════════ BLOG IDEAS ════════ -->
<div class="section">
<div class="section-header" onclick="toggleSection('blog-ideas-section', this)">
<h2>📝 Blog Post Ideas</h2>
<span class="section-count count">tick when live</span>
<span class="section-toggle collapsed"></span>
</div>
<div class="section-body" id="blog-ideas-section">
<div class="blog-idea-item">
<div class="blog-idea-checkbox">
<input type="checkbox" disabled checked />
</div>
<div class="blog-idea-body">
<div class="blog-idea-title">#001 — Why backing up an agent is so hard</div>
<div class="blog-idea-desc">Modern AI agents install packages everywhere — pip in venvs, npm globally, configs scattered across /etc, ~/.config, /opt, custom paths. A naive filesystem backup misses half the environment.</div>
<div class="blog-idea-meta">
<span class="blog-idea-tag">Technical</span>
<span class="blog-idea-tag">Backups</span>
<span class="blog-idea-tag">DevOps</span>
</div>
</div>
</div>
<div class="blog-idea-item" style="margin-top:12px">
<div class="blog-idea-checkbox">
<input type="checkbox" />
</div>
<div class="blog-idea-body">
<div class="blog-idea-title">#002 — Cold email at scale: from one-off to campaign</div>
<div class="blog-idea-desc">Moving from single cold emails to sequenced campaigns with follow-ups, A/B testing, reply detection, and CRM integration.</div>
<div class="blog-idea-meta">
<span class="blog-idea-tag">Marketing</span>
<span class="blog-idea-tag">Cold Email</span>
<span class="blog-idea-tag">Automation</span>
</div>
</div>
</div>
<div class="blog-idea-item" style="margin-top:12px">
<div class="blog-idea-checkbox">
<input type="checkbox" />
</div>
<div class="blog-idea-body">
<div class="blog-idea-title">#003 — Odoo + AI agent: beyond the speed run</div>
<div class="blog-idea-desc">Deeper Odoo workflows: auto-follow-up on stale leads, low-stock alerts, monthly sales reports — all via the agent.</div>
<div class="blog-idea-meta">
<span class="blog-idea-tag">Odoo</span>
<span class="blog-idea-tag">Integration</span>
<span class="blog-idea-tag">CRM</span>
</div>
</div>
</div>
<p style="margin-top:16px;font-size:0.8rem;color:#666">
Research notes at <code style="font-size:0.8rem">blog/ideas/###-topic.md</code>
</p>
</div>
</div>
</div>
<script>
/* ════════ DATA ════════ */
const goals = [
{ id: 'active-contacts', label: 'Active Contacts', current: 1, target: 60, note: 'CRM Cold → Won (88 on Hold excluded)' },
{ id: 'subscriptions', label: 'Subscriptions', current: 0, target: 50, note: 'Paying customers' },
{ id: 'influencers', label: 'Influencers', current: 0, target: 20, note: 'YT creators & affiliates' },
{ id: 'visitors', label: 'Page Visitors', current: 0, target: 10000, unit: '/mo', note: 'Monthly unique visitors' },
{ id: 'outreach-sent', label: 'Cold Emails Sent', current: 0, target: 100, note: 'First touch sent' },
{ id: 'crm-contacts', label: 'CRM Contacts', current: 89, target: 200, note: 'Total collected' },
];
const campaigns = [
{
label: '✉ Cold Mail', status: 'draft',
metrics: [{ v: 0, l: 'Drafted' }, { v: 0, l: 'Sent' }, { v: 0, l: 'Replied' }],
items: [
{ t: 'Coupons ready: friends950, creator950', ok: true },
{ t: 'Template signed as Mark', ok: true },
{ t: 'No contacts with real emails yet', ok: false },
]
},
{
label: '🎙 Influencer', status: 'paused',
metrics: [{ v: 60, l: 'Identified' }, { v: 0, l: 'Contacted' }, { v: 0, l: 'Onboarded' }],
items: [
{ t: '60 YT creators on Hold', ok: true },
{ t: 'Need business emails for top 20', ok: false },
{ t: 'Affiliate program TBD', ok: false },
]
},
{
label: '🔗 LinkedIn', status: 'draft',
metrics: [{ v: 0, l: 'Connections' }, { v: 0, l: 'Outreach' }, { v: 0, l: 'Replied' }],
items: [
{ t: 'No outreach started', ok: false },
{ t: 'Company page TBD', ok: false },
{ t: 'No content strategy defined', ok: false },
]
},
{
label: '🌐 SEO & Traffic', status: 'draft',
metrics: [{ v: 0, l: 'Visitors/mo' }, { v: 0, l: 'Blog Posts' }, { v: 0, l: 'Backlinks' }],
items: [
{ t: 'Plausible analytics live with 7 custom events', ok: true },
{ t: 'Need Google Search Console submission', ok: false },
{ t: 'Need blog posts (guest posts + own content)', ok: false },
{ t: 'Directory listings: Product Hunt, BetaList, G2', ok: false },
]
},
{
label: '🔗 Backlinks', status: 'draft',
metrics: [{ v: 0, l: 'Quality Links' }, { v: 0, l: 'Ref Domains' }, { v: 0, l: 'Guest Posts' }],
target: { links: 10, domains: 5, posts: 3 },
items: [
{ t: 'Need competitor backlink gap analysis', ok: false },
{ t: 'Guest post pitches to AI/DevOps blogs', ok: false },
{ t: 'HARO / Qwoted alerts for journalist queries', ok: false },
{ t: 'Directory submissions: Product Hunt, BetaList, G2', ok: false },
]
}
];
/* Top 5 needs — 2 done after Oliver's last update */
const NEEDS = [
{ id: "n1", text: "Business emails for top 20 YouTube creators — blocking cold email outreach", done: false },
{ id: "n2", text: "Coupon codes ready: creator950 ($9.50), friends950 ($9.50) — unlimited, includes OpenRouter credits", done: true },
{ id: "n3", text: "Product demo video (3 min screen recording)", done: false },
{ id: "n4", text: "Social media account access (X/Twitter + LinkedIn posting)", done: false },
{ id: "n5", text: "'Why I built Derez.ai' story — available on homepage", done: true },
];
/* ── Infos (action plan: 10K visitors/mo) ── */
const infos = [
{ label: 'YouTube Channel', value: '<a href="https://www.youtube.com/@derez_ai" target="_blank" style="color:#00f5ff">youtube.com/@derez_ai</a> — Deploy your own AI agent in 5 minutes. Speed runs, tutorials, Odoo integrations, and AI agent demos. Banner saved at assets/images/youtube-banner.svg (2048x1152).' },
{ label: 'Why not start?', value: 'No Search Console access yet — can\'t index site. No blog content drafted. No directory submissions. Need to unblock these first, then launch is days not weeks.' },
{ label: 'Blog Strategy', value: '2 posts/week minimum. Topics: "Deploy AI agents in 5 min" (tutorial), "Hermes agent setup guide" (tutorial), "Why agent hosting beats DIY" (comparison), "Cold email automation stack" (case study). Cross-post to Medium/Dev.to. Each post = 1 backlink target + long-tail SEO. Start with 4 cornerstone posts before directory submissions.' },
{ label: 'Phase 1 (Week 1)', value: 'Get Search Console access. Draft 2 cornerstone blog posts. Submit to Product Hunt + BetaList + AlternativeTo. Set up HARO/Qwoted alerts.' },
{ label: 'Phase 2 (Week 2)', value: 'Publish 2 posts. Guest post pitch to 5 AI/DevOps blogs. Start LinkedIn company page + 50 connection requests/week.' },
{ label: 'Phase 3 (Week 3-4)', value: 'Publish 4 more posts. Follow up guest post pitches. Directory listings done. Target: 500 unique visitors by end of month.' },
{ label: '10K Breakdown', value: 'SEO (organic): 40% = 4K. Direct/referral (directory + backlinks): 25% = 2.5K. Social (LinkedIn/X): 20% = 2K. Email/newsletter: 15% = 1.5K. Realistic 3-4 month trajectory.' },
];
/* ── Filtered comms — spam/sales/ads/work-for-you removed ── */
function isSpam(email) {
const lower = (email.subj + ' ' + email.from + ' ' + (email.detail || '')).toLowerCase();
const spamPatterns = [
'spam', 'advertisement', 'advert', 'work for you', 'work from home',
'make money', 'earn', 'investment opportunity', 'click here',
'buy now', 'limited offer', 'free money', 'congratulations',
'you\'ve won', 'marketing offer', 'promotion', 'partner offer',
'guest post', 'seo services', 'link building', 'content writing services',
];
return spamPatterns.some(p => lower.includes(p));
}
const rawInbox = [
{ id: 'i1', time: '14:44', from: 'Mail Delivery System', subj: 'Undelivered — the@123.de', detail: '421 Downstream server error — cold email bounced', alert: true, spam: true },
{ id: 'i2', time: '21:04', from: 'Mail Delivery System', subj: 'Undelivered — the@123.de', detail: '421 Downstream server error', alert: false, spam: true },
{ id: 'i3', time: '08:12', from: 'seo@boostmyrank.xyz', subj: 'Guest post opportunity for your site', detail: 'Wants to publish sponsored content', spam: true },
{ id: 'i4', time: '19:33', from: 'hannah@freelance.io', subj: 'I can help with your content marketing', detail: 'Offering writing services', spam: true },
];
const rawSent = [
{ time: 'Jun 09', to: 'o.arnold@projektbox.de', subj: 'setting up an agent for projektbox', spam: false },
{ time: 'Jun 08', to: 'oliver@odoo4projects.com', subj: 'managing your agents without SSH', spam: false },
];
const comms = {
inbox: {
total: rawInbox.filter(i => !i.spam).length,
total_spam_ignored: rawInbox.filter(i => i.spam).length,
items: rawInbox.filter(i => !i.spam),
},
sent: {
total: rawSent.filter(i => !i.spam).length,
items: rawSent.filter(i => !i.spam),
top_sent: 'Mark — Customer Success Agent'
},
note: 'Spam/sales filtered out. Only actionable items shown.'
};
/* ════════ RENDER ════════ */
function fmtTimestamp(date) {
const d = date || new Date();
const y = d.getFullYear();
const mo = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
return `${y}-${mo}-${day} ${h}:${mi}`;
}
function pct(cur, target) { return target > 0 ? Math.min(100, Math.round(cur / target * 100)) : 0; }
function barClass(p) { return p >= 80 ? 'green' : p >= 40 ? 'blue' : p >= 20 ? 'yellow' : 'red'; }
function toggleSection(id, header) {
const el = document.getElementById(id);
const toggle = header.querySelector('.section-toggle');
el.classList.toggle('open');
toggle.classList.toggle('collapsed');
}
function renderGoals() {
document.getElementById('goals-grid').innerHTML = goals.map(g => {
const p = pct(g.current, g.target);
const bc = barClass(p);
return `<div class="goal-card">
<div class="goal-label">${g.label}</div>
<div class="goal-current">${g.current}<span class="goal-target"> / ${g.target}${g.unit ? ' ' + g.unit : ''}</span></div>
<div class="goal-bar"><div class="goal-bar-fill ${bc}" style="width:${p}%"></div></div>
<div class="goal-sub">${g.note}</div>
</div>`;
}).join('');
}
function renderCampaigns() {
document.getElementById('campaign-grid').innerHTML = campaigns.map(c => {
const st = c.status === 'active' ? '<span class="status-tag active">● Active</span>'
: c.status === 'paused' ? '<span class="status-tag paused">● Paused</span>'
: '<span class="status-tag draft">Draft</span>';
return `<div class="campaign-card">
<h3>${c.label} ${st}</h3>
<div class="stat-row">${c.metrics.map(m => `<div class="stat-item"><div class="stat-num">${m.v}</div><div class="stat-label">${m.l}</div></div>`).join('')}</div>
${c.target ? `<div style="font-size:10px;color:var(--text-muted);margin-bottom:6px;">Target: ${c.target.links} quality links, ${c.target.domains} referring domains, ${c.target.posts} guest posts</div>` : ''}
<ul>${c.items.map(i => `<li><span class="dot ${i.ok ? 'green' : 'red'}"></span>${i.t}</li>`).join('')}</ul>
</div>`;
}).join('');
}
/* ── Needs (checkboxes) ── */
function getNeeds() {
try {
const saved = localStorage.getItem("derez-needs");
if (saved) {
const parsed = JSON.parse(saved);
return NEEDS.map(def => {
const s = parsed.find(p => p.id === def.id);
return s ? { ...def, done: s.done } : def;
});
}
} catch(e) {}
return NEEDS;
}
function toggleNeed(id) {
const needs = getNeeds();
const idx = needs.findIndex(n => n.id === id);
if (idx === -1) return;
needs[idx].done = !needs[idx].done;
localStorage.setItem("derez-needs", JSON.stringify(needs));
renderNeeds(needs);
}
function renderNeeds(needs) {
const list = document.getElementById("needs-list");
list.innerHTML = needs.map(n => `
<li class="need-item${n.done ? ' done' : ''}">
<span class="need-checkbox${n.done ? ' checked' : ''}" data-id="${n.id}" onclick="toggleNeed('${n.id}')"></span>
<span class="need-text">${n.text}</span>
</li>
`).join("");
}
function renderCommunications() {
const c = comms;
const inboxItems = c.inbox.items.map(i =>
`<li><span class="comms-time">${i.time}</span><span><span class="comms-from">${i.from}</span><br><span class="comms-subj">${i.subj}</span>${i.detail ? ' <span class="comms-detail">' + i.detail + '</span>' : ''}</span></li>`
).join('');
const alerts = c.inbox.items.filter(i => i.alert).map(a =>
`<div class="comms-alert"><span class="alert-icon">⚠</span><div><div class="alert-body">${a.body || a.subj}</div><div class="alert-detail">${a.detail || ''}</div></div></div>`
).join('');
const sentItems = c.sent.items.map(i =>
`<li><span class="comms-time">${i.time}</span><span><span class="comms-from">${i.to}</span><br><span class="comms-subj">${i.subj}</span></span></li>`
).join('');
const spamNote = comms.inbox.total_spam_ignored > 0
? `<div style="margin-top:8px;font-size:11px;color:var(--text-muted);">🧹 ${comms.inbox.total_spam_ignored} spam/sales filtered out</div>`
: '';
document.getElementById('comms-grid').innerHTML =
`<div class="comms-card">
<h3>📥 Inbox</h3>
<div class="comms-summary">
<div class="comms-stat"><div class="comms-stat-num">${c.inbox.total}</div><div class="comms-stat-label">Actionable</div></div>
<div class="comms-stat"><div class="comms-stat-num">${c.inbox.items.filter(i => i.alert).length}</div><div class="comms-stat-label">Alerts</div></div>
</div>
<ul class="comms-list">${inboxItems}</ul>
${alerts}
${spamNote}
</div>
<div class="comms-card">
<h3>📤 Sent</h3>
<div class="comms-summary">
<div class="comms-stat"><div class="comms-stat-num">${c.sent.total}</div><div class="comms-stat-label">Sent</div></div>
</div>
<ul class="comms-list">${sentItems}</ul>
<div style="margin-top:8px;font-size:11px;color:var(--text-muted);">
From: ${c.sent.top_sent} &middot; ${c.note}
</div>
</div>`;
}
function renderInfos() {
document.getElementById('infos-list').innerHTML = infos.map(i =>
`<li><span class="info-label">${i.label}</span><span class="info-value">${i.value}</span></li>`
).join('');
}
async function fetchLiveData() {
try {
const res = await fetch('data/operations.json?t=' + Date.now());
if (!res.ok) throw new Error('Not found');
const d = await res.json();
if (d.date) document.getElementById('brief-date').textContent = d.date;
if (d.updated) document.getElementById('brief-updated').textContent = 'Last updated: ' + d.updated;
if (d.goals) d.goals.forEach((g, i) => { if (goals[i]) { goals[i].current = g.current; goals[i].note = g.note || goals[i].note; } });
renderGoals();
} catch { /* defaults */ }
}
/* ── Init ── */
document.getElementById('brief-date').textContent = fmtTimestamp();
renderGoals();
renderCampaigns();
renderNeeds(getNeeds());
renderCommunications();
renderInfos();
fetchLiveData();
</script>
</body>
</html>