Files
derez.ai/operations.html
T
2026-06-09 19:49:14 -03:00

916 lines
36 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; }
}
</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>
</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' }],
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 (answers Oliver asked for, stored here not chat) ── */
const infos = [
{ label: 'Coupon Codes', value: 'creator950 ($9.50 off), friends950 ($9.50 off) — unlimited use, OpenRouter credits still apply' },
{ label: 'Cold Email', value: 'Signed as Mark — Customer Success Agent. Templates reviewed via Telegram before send.' },
{ label: 'Influencer Offer', value: 'Free 3-month agent + 10% recurring affiliate commission. Pitch: "Deploy AI agents in 5 min — no DevOps."' },
{ label: 'Site Status', value: 'derez.ai live. Prelaunch. 89 CRM contacts (59 YT creators on Hold).' },
{ label: 'Timezone', value: 'America/Asuncion (PYST, UTC-3/UTC-4). Daily brief 09:00.' },
];
/* ── 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>
<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>