prelaunch: create launch dashboard + daily cron updates

- New launch-dashboard.html: KPI grid, 14-day action plan, strategy
  overview, items-needed checklist, daily routine, cron schedule
- Nav link '🚀 Launch' added to main site header
- data/launch-status.json: data file updated daily at 03:00 by cron
- scripts/update-dashboard.py: advances day, probes KPIs, marks
  today's actions
- cron jobs configured:
  • 03:00 daily — dashboard update script + git commit/push
  • 09:00 daily — 1-hour working session reminder
This commit is contained in:
Oliver
2026-06-09 14:03:44 -03:00
parent d0d9f7f95e
commit 3d1c9be1d0
4 changed files with 1425 additions and 0 deletions
+977
View File
@@ -0,0 +1,977 @@
<!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>Prelaunch Dashboard — Derez.ai</title>
<style>
/* ═══════════════════════════════════════════════════════════════
DESIGN TOKENS (matches derez.ai design system)
═══════════════════════════════════════════════════════════════ */
:root {
--bg: #000810;
--bg-card: #010f20;
--bg-inner: #000d1a;
--bg-hover: #001a30;
--bg-input: #000813;
--border: #0a3060;
--border-hi: rgba(0,245,255,0.45);
--accent: #00f5ff;
--accent-dim: #002535;
--danger: #ff3b3b;
--danger-dim: #3b0000;
--success: #00ff88;
--warning: #ffaa00;
--text: #c8f0ff;
--text-muted: #2e6a80;
--radius: 8px;
--radius-card: 18px;
--shadow: 0 0 0 1px rgba(0,245,255,0.25), 0 0 60px rgba(0,245,255,0.12), 0 28px 72px rgba(0,0,0,0.9);
--max-width: 1200px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }
body {
font-family: "Courier New", Courier, monospace;
background: var(--bg);
color: var(--text);
font-size: 14px;
line-height: 1.6;
overflow-x: hidden;
padding-top: 70px;
}
body::before {
content: "";
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 60% 50% at 25% 20%, rgba(0,245,255,0.06) 0%, transparent 70%),
radial-gradient(ellipse 50% 60% at 75% 80%, rgba(0,200,255,0.04) 0%, transparent 70%);
pointer-events: none;
z-index: 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;
position: relative;
z-index: 1;
}
/* ── Header ──────────────────────────────── */
.site-header {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
background: rgba(0,8,16,0.85);
backdrop-filter: blur(14px);
border-bottom: 1px solid var(--border-hi);
height: 60px;
display: flex;
align-items: center;
}
.site-header .container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.site-logo { display: flex; align-items: center; flex-shrink: 0; }
.site-logo-img { display: block; height: 30px; width: auto; }
.nav-links {
display: flex;
align-items: center;
gap: 8px;
list-style: none;
}
.nav-links a {
padding: 6px 14px;
border-radius: var(--radius);
color: var(--text-muted);
font-size: 13px;
font-weight: 500;
transition: color 0.15s, background 0.15s;
}
.nav-links a:hover {
color: var(--text);
background: var(--bg-hover);
text-decoration: none;
}
.nav-links a.active {
color: var(--accent);
background: var(--accent-dim);
}
/* ── Status Banner ───────────────────────── */
.status-banner {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 28px 32px;
margin-bottom: 28px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.status-banner h1 {
font-size: 22px;
color: var(--accent);
font-weight: 700;
}
.status-banner .day-indicator {
font-size: 13px;
color: var(--text-muted);
margin-top: 4px;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.status-badge.live {
background: rgba(0,255,136,0.12);
color: var(--success);
border: 1px solid rgba(0,255,136,0.3);
}
.status-badge.paused {
background: rgba(255,170,0,0.12);
color: var(--warning);
border: 1px solid rgba(255,170,0,0.3);
}
.last-updated {
font-size: 11px;
color: var(--text-muted);
}
/* ── Section ─────────────────────────────── */
.section {
margin-bottom: 28px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
cursor: pointer;
user-select: none;
}
.section-header h2 {
font-size: 16px;
font-weight: 700;
color: var(--text);
display: flex;
align-items: center;
gap: 10px;
}
.section-header h2 .icon { color: var(--accent); }
.section-toggle {
color: var(--text-muted);
font-size: 18px;
transition: transform 0.2s;
}
.section-toggle.collapsed {
transform: rotate(-90deg);
}
.section-body {
transition: max-height 0.3s ease, opacity 0.2s ease;
overflow: hidden;
max-height: 20000px;
opacity: 1;
}
.section-body.hidden {
max-height: 0;
opacity: 0;
padding: 0;
}
/* ── KPI Grid ────────────────────────────── */
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.kpi-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 20px;
}
.kpi-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
margin-bottom: 6px;
}
.kpi-value {
font-size: 28px;
font-weight: 700;
color: var(--text);
}
.kpi-value .target {
font-size: 16px;
color: var(--text-muted);
font-weight: 400;
}
.kpi-bar {
margin-top: 10px;
height: 4px;
background: var(--bg-inner);
border-radius: 2px;
overflow: hidden;
}
.kpi-bar-fill {
height: 100%;
border-radius: 2px;
transition: width 0.6s ease;
}
.kpi-bar-fill.green { background: var(--success); }
.kpi-bar-fill.blue { background: var(--accent); }
.kpi-bar-fill.yellow { background: var(--warning); }
.kpi-bar-fill.red { background: var(--danger); }
.kpi-sub {
font-size: 11px;
color: var(--text-muted);
margin-top: 4px;
}
/* ── Action Plan Table ───────────────────── */
.action-table {
width: 100%;
border-collapse: collapse;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
overflow: hidden;
font-size: 13px;
}
.action-table th {
background: var(--bg-inner);
padding: 12px 16px;
text-align: left;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
}
.action-table td {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.action-table tr:last-child td { border-bottom: none; }
.action-table td.day-col {
color: var(--accent);
font-weight: 600;
white-space: nowrap;
width: 60px;
}
.action-table td.status-col {
white-space: nowrap;
width: 110px;
}
.action-table td.owner-col { white-space: nowrap; width: 80px; }
.status-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
}
.status-tag.done { background: rgba(0,255,136,0.12); color: var(--success); border: 1px solid rgba(0,255,136,0.25); }
.status-tag.in-progress { background: rgba(0,245,255,0.12); color: var(--accent); border: 1px solid rgba(0,245,255,0.25); }
.status-tag.pending { background: rgba(46,106,128,0.15); color: var(--text-muted); border: 1px solid transparent; }
.status-tag.blocked { background: rgba(255,59,59,0.12); color: var(--danger); border: 1px solid rgba(255,59,59,0.25); }
.status-tag.today { background: rgba(255,170,0,0.15); color: var(--warning); border: 1px solid rgba(255,170,0,0.3); }
/* ── Card Layout ─────────────────────────── */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
.info-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 24px;
}
.info-card h3 {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.info-card ul {
list-style: none;
padding: 0;
}
.info-card li {
padding: 8px 0;
border-bottom: 1px solid rgba(10,48,96,0.4);
font-size: 13px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.info-card li:last-child { border-bottom: none; }
.info-card li .bullet { color: var(--accent); flex-shrink: 0; }
.need-checkbox {
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-radius: 4px;
flex-shrink: 0;
margin-top: 2px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.15s;
}
.need-checkbox.checked {
background: var(--success);
border-color: var(--success);
}
.need-checkbox.checked::after {
content: "✓";
color: var(--bg);
font-size: 12px;
font-weight: 700;
}
.need-item.done .need-text {
color: var(--text-muted);
text-decoration: line-through;
}
/* ── Strategy Section ────────────────────── */
.strategy-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 24px;
margin-bottom: 16px;
}
.strategy-card h3 {
font-size: 14px;
color: var(--accent);
margin-bottom: 10px;
}
.strategy-card p, .strategy-card ul {
font-size: 13px;
color: var(--text);
line-height: 1.7;
}
.strategy-card ul {
padding-left: 20px;
list-style: none;
}
.strategy-card ul li::before {
content: " ";
color: var(--accent);
}
/* ── Cron / Daily Routine ────────────────── */
.cron-timeline {
position: relative;
padding-left: 28px;
}
.cron-timeline::before {
content: "";
position: absolute;
left: 8px;
top: 4px;
bottom: 4px;
width: 2px;
background: var(--border);
}
.cron-item {
position: relative;
padding: 6px 0 16px;
}
.cron-item::before {
content: "";
position: absolute;
left: -24px;
top: 10px;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--bg-card);
border: 2px solid var(--accent);
}
.cron-item.daily-hour::before {
background: var(--accent);
box-shadow: 0 0 12px rgba(0,245,255,0.5);
}
.cron-time {
font-size: 12px;
color: var(--accent);
font-weight: 600;
}
.cron-title {
font-size: 14px;
font-weight: 600;
color: var(--text);
}
.cron-desc {
font-size: 12px;
color: var(--text-muted);
margin-top: 2px;
}
/* ── Responsive ──────────────────────────── */
@media (max-width: 900px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
.status-banner { flex-direction: column; align-items: flex-start; }
.action-table { font-size: 12px; }
.action-table th, .action-table td { padding: 8px 10px; }
}
@media (max-width: 500px) {
.kpi-grid { grid-template-columns: 1fr; }
.nav-links { display: none; }
}
.tag { color: var(--text-muted); font-size: 12px; }
.highlight { color: var(--accent); }
.success-text { color: var(--success); }
.warn-text { color: var(--warning); }
.danger-text { color: var(--danger); }
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 20px;
border-radius: var(--radius);
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.15s;
font-family: inherit;
}
.btn-primary {
background: var(--accent);
color: var(--bg);
}
.btn-primary:hover { opacity: 0.85; text-decoration: none; }
.btn-ghost {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
</style>
</head>
<body>
<!-- ── HEADER ──────────────────────────────────── -->
<header class="site-header">
<div class="container">
<a href="/" class="site-logo">
<img src="assets/images/logo.svg" alt="Derez.ai" width="132" height="34" class="site-logo-img" />
</a>
<ul class="nav-links" id="nav-links">
<li><a href="/">Home</a></li>
<li><a href="/#pricing">Pricing</a></li>
<li><a href="/#faq">FAQ</a></li>
<li><a href="launch-dashboard.html" class="active">🚀 Launch Dashboard</a></li>
</ul>
</div>
</header>
<div class="container">
<!-- ── STATUS BANNER ─────────────────────────── -->
<div class="status-banner" id="status-banner">
<div>
<h1>🚀 Prelaunch Dashboard</h1>
<div class="day-indicator" id="day-indicator">Day 1 of 14 — Counting down to launch</div>
</div>
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;">
<span class="status-badge live" id="campaign-status">● Campaign Active</span>
<span class="last-updated" id="last-updated">Updated: just now</span>
</div>
</div>
<!-- ── KPI DASHBOARD ─────────────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('kpi-section', this)">
<h2><span class="icon">📊</span> Key Performance Indicators</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="kpi-section">
<div class="kpi-grid" id="kpi-grid">
<!-- populated by JS -->
</div>
</div>
</div>
<!-- ── ACTION PLAN ───────────────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('actions-section', this)">
<h2><span class="icon">📋</span> 14-Day Action Plan</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="actions-section">
<table class="action-table">
<thead>
<tr>
<th>Day</th>
<th>Action</th>
<th>Owner</th>
<th>Status</th>
</tr>
</thead>
<tbody id="action-rows">
<!-- populated by JS -->
</tbody>
</table>
</div>
</div>
<!-- ── STRATEGY SUMMARY ──────────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('strategy-section', this)">
<h2><span class="icon">🎯</span> Strategy Overview</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="strategy-section">
<div class="grid-2">
<div>
<div class="strategy-card">
<h3>👨‍👩‍👧‍👦 Friends &amp; Family Program</h3>
<p>Invite 2030 trusted contacts to beta test at discounted rates. Each gets:</p>
<ul>
<li>50% off first month ($4.75/mo instead of $9.50)</li>
<li>$5 OpenRouter credits with referral coupon code</li>
<li>Priority support via chat</li>
<li>Direct feedback channel (Telegram group)</li>
<li>NPS survey after 7 days</li>
</ul>
<p style="margin-top:8px;"><span class="highlight">Goal:</span> 20 signups, 5 detailed testimonials</p>
</div>
<div class="strategy-card">
<h3>🔗 Backlinks &amp; SEO</h3>
<p>Three-pronged approach:</p>
<ul>
<li><strong>Product Hunt / directories:</strong> List on Product Hunt, BetaList, AlternativeTo, G2</li>
<li><strong>Guest posts:</strong> Pitch "How to deploy AI agents in 5 minutes" to AI/DevOps blogs</li>
<li><strong>HARO / Qwoted:</strong> Respond to journalist queries about AI hosting</li>
<li><strong>Open source:</strong> Link back from Hermes Agent docs / GitHub (if accepted)</li>
</ul>
<p style="margin-top:8px;"><span class="highlight">Goal:</span> 10 quality backlinks, rank in top 5 for "AI agent hosting"</p>
</div>
</div>
<div>
<div class="strategy-card">
<h3>📢 Influencer Outreach</h3>
<p>Target micro-influencers in AI/ML/DevOps spaces:</p>
<ul>
<li><strong>Where to find them:</strong> YouTube (AI channels 5K50K subs), X/Twitter (AI builders), LinkedIn (DevOps thought leaders), Discord communities (OpenRouter, Nous Research, local LLM)</li>
<li><strong>Offer:</strong> Free 3-month agent instance + affiliate commission (10% recurring)</li>
<li><strong>Pitch angle:</strong> "Your followers can deploy their own AI agent in 5 minutes — no DevOps needed"</li>
<li><strong>Toolkit:</strong> One-pager, demo video, coupon codes, pre-written tweets</li>
</ul>
<p style="margin-top:8px;"><span class="highlight">Goal:</span> 5 confirmed partnerships, 3 social mentions</p>
</div>
<div class="strategy-card">
<h3>📈 Launch Strategy</h3>
<ul>
<li><strong>Week 1 (Days 17):</strong> Friends &amp; Family + SEO foundation + directory listings</li>
<li><strong>Week 2 (Days 814):</strong> Influencer push + guest posts + social content sprint</li>
<li><strong>Launch day:</strong> Product Hunt + email blast + social storm + all influencers post</li>
<li><strong>Tracking:</strong> UTM on every link, Google Search Console, weekly KPI review</li>
</ul>
<p style="margin-top:8px;"><span class="highlight">Goal:</span> 500 unique visitors, 50 email captures, 20 paid signups by launch</p>
</div>
</div>
</div>
</div>
</div>
<!-- ── DAILY ROUTINE & CRON ──────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('routine-section', this)">
<h2><span class="icon"></span> Daily Routine &amp; Cron Schedule</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="routine-section">
<div class="grid-2">
<div class="info-card">
<h3>⏱ Our Daily 1-Hour Block</h3>
<p style="font-size:13px;margin-bottom:12px;">Every day at <strong class="highlight">09:00 PYST</strong> (America/Asuncion) — 60 minutes.</p>
<div class="cron-timeline">
<div class="cron-item daily-hour">
<div class="cron-time">09:00 09:05</div>
<div class="cron-title">Dashboard review</div>
<div class="cron-desc">Read the 03:00 auto-update, review KPIs, see what changed overnight</div>
</div>
<div class="cron-item daily-hour">
<div class="cron-time">09:05 09:15</div>
<div class="cron-title">Status sync</div>
<div class="cron-desc">Reply to dashboard comments, update action statuses, unblock items</div>
</div>
<div class="cron-item daily-hour">
<div class="cron-time">09:15 09:45</div>
<div class="cron-title">Deep work</div>
<div class="cron-desc">Execute today's priority actions (outreach emails, content drafts, influencer DMs)</div>
</div>
<div class="cron-item daily-hour">
<div class="cron-time">09:45 10:00</div>
<div class="cron-title">Wrap &amp; queue</div>
<div class="cron-desc">Set tomorrow's priorities, queue tasks for the cron, log progress</div>
</div>
</div>
</div>
<div class="info-card">
<h3>🤖 Automated Cron Jobs</h3>
<div class="cron-timeline">
<div class="cron-item">
<div class="cron-time">03:00 PYST</div>
<div class="cron-title">📊 Dashboard Update</div>
<div class="cron-desc">Auto-generates KPI snapshot: signups, traffic, backlinks, influencer status. Writes to <code>data/launch-status.json</code> for Oliver to review at 09:00.</div>
</div>
<div class="cron-item">
<div class="cron-time">03:10 PYST</div>
<div class="cron-title">📈 SEO &amp; Rank Tracker</div>
<div class="cron-desc">Check Google Search Console for new impressions/clicks. Log keyword positions for target terms. Alert on +50% change.</div>
</div>
<div class="cron-item">
<div class="cron-time">03:20 PYST</div>
<div class="cron-title">🔗 Backlink Monitor</div>
<div class="cron-desc">Check Ahrefs/Moz API for new backlinks. Update backlink KPI count. Flag new domains referring.</div>
</div>
<div class="cron-item">
<div class="cron-time">03:30 PYST</div>
<div class="cron-title">💬 Social Monitor</div>
<div class="cron-desc">Check X/Twitter for brand mentions, reply to inquiries, log sentiment.</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ── ITEMS NEEDED ──────────────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('items-section', this)">
<h2><span class="icon">📦</span> Items Needed From You</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="items-section">
<div class="info-card">
<p style="margin-bottom:16px;font-size:13px;color:var(--text-muted);">
Check off items as you deliver them. This list is saved in your browser.
</p>
<ul id="needs-list">
<!-- populated by JS -->
</ul>
</div>
</div>
</div>
<!-- ── OUTCOME TARGETS ───────────────────────── -->
<div class="section">
<div class="section-header" onclick="toggleSection('targets-section', this)">
<h2><span class="icon">🏆</span> 14-Day Targets</h2>
<span class="section-toggle"></span>
</div>
<div class="section-body" id="targets-section">
<div class="grid-3">
<div class="info-card">
<h3>👥 Friends &amp; Family</h3>
<ul>
<li><span class="bullet"></span> 20 beta signups</li>
<li><span class="bullet"></span> 5 video testimonials</li>
<li><span class="bullet"></span> 8 NPS responses (target: 8+)</li>
<li><span class="bullet"></span> 3 bug/UX improvement reports</li>
</ul>
</div>
<div class="info-card">
<h3>📣 Reach &amp; Visibility</h3>
<ul>
<li><span class="bullet"></span> 5 influencer partnerships</li>
<li><span class="bullet"></span> 10 quality backlinks</li>
<li><span class="bullet"></span> 500 unique landing page visitors</li>
<li><span class="bullet"></span> 50 email list signups</li>
</ul>
</div>
<div class="info-card">
<h3>📈 SEO &amp; Rankings</h3>
<ul>
<li><span class="bullet"></span> Indexed in Google (&lt;7 days)</li>
<li><span class="bullet"></span> Top 5 for "AI agent hosting"</li>
<li><span class="bullet"></span> 3 directory listings live</li>
<li><span class="bullet"></span> 2 guest posts published</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<br><br>
<script>
/* ═══════════════════════════════════════════════════════════════════
DATA — strategy & action plan
═══════════════════════════════════════════════════════════════════ */
const DAY_ONE = new Date("2026-06-09T00:00:00-04:00"); // today
const KPIS = [
{ id: "signups", label: "Friends & Family Signups", current: 0, target: 20, unit: "", color: "green" },
{ id: "visitors", label: "Landing Page Visitors", current: 0, target: 500, unit: "", color: "blue" },
{ id: "emails", label: "Email Captures", current: 0, target: 50, unit: "", color: "blue" },
{ id: "backlinks", label: "Quality Backlinks", current: 0, target: 10, unit: "", color: "yellow" },
{ id: "influencers", label: "Influencer Partnerships", current: 0, target: 5, unit: "", color: "green" },
{ id: "testimonials", label: "Testimonials Collected", current: 0, target: 5, unit: "", color: "green" },
{ id: "impressions", label: "Google Impressions (7d)", current: 0, target: 2000, unit: "", color: "blue" },
{ id: "directory", label: "Directory Listings", current: 0, target: 3, unit: "", color: "yellow" },
];
const ACTIONS = [
// ── Day 1 ──
{ day: 1, action: "Set up friends & family invite list (2030 contacts)", owner: "Oliver", status: "today" },
{ day: 1, action: "Generate & configure referral coupon codes in Stripe", owner: "Mint", status: "today" },
{ day: 1, action: "Create prelaunch landing page / launch-dashboard", owner: "Mint", status: "done" },
{ day: 1, action: "Submit to Google Search Console + Bing Webmaster Tools", owner: "Mint", status: "pending" },
{ day: 1, action: "Set up UTM tracking for all campaign links", owner: "Mint", status: "pending" },
// ── Day 2 ──
{ day: 2, action: "Send friends & family invitation emails", owner: "Oliver", status: "pending" },
{ day: 2, action: "Create F&F onboarding docs (1-pager + video)", owner: "Mint", status: "pending" },
{ day: 2, action: "Set up dedicated F&F Telegram/Discord group", owner: "Mint", status: "pending" },
{ day: 2, action: "Submit to Product Hunt — create draft listing", owner: "Oliver", status: "pending" },
// ── Day 3 ──
{ day: 3, action: "Start influencer research: compile list of 30 targets", owner: "Oliver", status: "pending" },
{ day: 3, action: "Write influencer outreach email template", owner: "Mint", status: "pending" },
{ day: 3, action: "Submit to BetaList, AlternativeTo, G2 directories", owner: "Mint", status: "pending" },
// ── Day 4 ──
{ day: 4, action: "Send first batch of influencer DMs/emails (top 10)", owner: "Oliver", status: "pending" },
{ day: 4, action: "Pitch guest post: 'Deploy AI agents in 5 min' to 5 blogs", owner: "Mint", status: "pending" },
{ day: 4, action: "Set up HARO / Qwoted / Connectively alerts", owner: "Mint", status: "pending" },
{ day: 4, action: "First friends & family onboardings go live", owner: "Oliver", status: "pending" },
// ── Day 5 ──
{ day: 5, action: "Follow up on influencer DMs (non-responders)", owner: "Oliver", status: "pending" },
{ day: 5, action: "Create 3 social posts (X/LinkedIn) about launch", owner: "Mint", status: "pending" },
{ day: 5, action: "Send F&F NPS survey #1", owner: "Mint", status: "pending" },
// ── Day 6 ──
{ day: 6, action: "Publish first social proof tweet: F&F testimonial", owner: "Oliver", status: "pending" },
{ day: 6, action: "Write blog post: 'Why I built Derez.ai'", owner: "Oliver", status: "pending" },
{ day: 6, action: "Check Google indexing status — escalate if not indexed", owner: "Mint", status: "pending" },
// ── Day 7 ──
{ day: 7, action: "Mid-campaign review: KPI check vs targets", owner: "Both", status: "pending" },
{ day: 7, action: "Second batch influencer outreach (next 10 targets)", owner: "Oliver", status: "pending" },
{ day: 7, action: "Polish Product Hunt listing with F&F feedback quotes", owner: "Mint", status: "pending" },
// ── Day 8 ──
{ day: 8, action: "Guest post #1 published — monitor backlinks", owner: "Mint", status: "pending" },
{ day: 8, action: "Send affiliate program details to confirmed influencers", owner: "Oliver", status: "pending" },
{ day: 8, action: "Content sprint: write 5 more social posts", owner: "Mint", status: "pending" },
// ── Day 9 ──
{ day: 9, action: "Follow up guest post pitches — pitch 5 more blogs", owner: "Mint", status: "pending" },
{ day: 9, action: "Record demo video (screen recording 3 min)", owner: "Oliver", status: "pending" },
{ day: 9, action: "Email list: send first newsletter teaser", owner: "Mint", status: "pending" },
// ── Day 10 ──
{ day: 10, action: "Launch day prep: schedule Product Hunt live", owner: "Oliver", status: "pending" },
{ day: 10, action: "Coordinate influencer posts for launch day", owner: "Both", status: "pending" },
{ day: 10, action: "Prepare launch day email blast", owner: "Mint", status: "pending" },
// ── Day 11 ──
{ day: 11, action: "🚀 LAUNCH DAY — Product Hunt go-live", owner: "Both", status: "pending" },
{ day: 11, action: "Social storm: all platforms post simultaneously", owner: "Oliver", status: "pending" },
{ day: 11, action: "Influencers publish their content", owner: "Influencers", status: "pending" },
// ── Day 12 ──
{ day: 12, action: "Post-launch: reply to every Product Hunt comment", owner: "Oliver", status: "pending" },
{ day: 12, action: "Monitor signups & traffic spike — adjust messaging", owner: "Mint", status: "pending" },
// ── Day 13 ──
{ day: 13, action: "Follow up on all pending leads from launch", owner: "Oliver", status: "pending" },
{ day: 13, action: "Send thank-you emails to F&F + influencers", owner: "Mint", status: "pending" },
// ── Day 14 ──
{ day: 14, action: "Final KPI review & retrospective", owner: "Both", status: "pending" },
{ day: 14, action: "Compile lessons learned doc", owner: "Mint", status: "pending" },
{ day: 14, action: "Decide: full public launch or iterate based on data", owner: "Oliver", status: "pending" },
];
const NEEDS = [
{ id: "n1", text: "List of 2030 friends & family contacts (name + email)", done: false },
{ id: "n2", text: "Target influencer list (30 names, handles/emails, niche, follower count)", done: false },
{ id: "n3", text: "Influencer partnership budget (free plans? cash? affiliate %?)", done: false },
{ id: "n4", text: "OpenRouter coupon codes for referral program (5 needed)", done: false },
{ id: "n5", text: "Product demo video (3 min screen recording) or go-ahead to record one", done: false },
{ id: "n6", text: "35 screenshots of dashboard / agent setup flow", done: false },
{ id: "n7", text: "Social media account access (X/Twitter + LinkedIn posting access)", done: false },
{ id: "n8", text: "Personal story / 'Why I built Derez.ai' notes for blog post", done: false },
{ id: "n9", text: "Competitor list (510 names) for backlink gap analysis", done: false },
{ id: "n10", text: "Price anchoring: confirm if F&F get special discount or free month", done: false },
{ id: "n11", text: "HARO / Qwoted / Connectively account (or credentials to set up)", done: false },
{ id: "n12", text: "Google Search Console access for derez.ai", done: true },
];
/* ═══════════════════════════════════════════════════════════════════
RENDER
═══════════════════════════════════════════════════════════════════ */
function getDayNumber() {
const now = new Date();
const diff = Math.floor((now - DAY_ONE) / (1000 * 60 * 60 * 24));
return Math.max(1, Math.min(14, diff + 1));
}
function renderDay() {
const day = getDayNumber();
document.getElementById("day-indicator").textContent =
`Day ${day} of 14 — ${14 - day} days until launch`;
}
function renderKPIs(kpis) {
const grid = document.getElementById("kpi-grid");
grid.innerHTML = kpis.map(k => {
const pct = k.target > 0 ? Math.min(100, Math.round((k.current / k.target) * 100)) : 0;
const barColor = k.color || "blue";
return `
<div class="kpi-card">
<div class="kpi-label">${k.label}</div>
<div class="kpi-value">${k.current} <span class="target">/ ${k.target}${k.unit}</span></div>
<div class="kpi-bar"><div class="kpi-bar-fill ${barColor}" style="width:${pct}%"></div></div>
<div class="kpi-sub">${pct}% of target</div>
</div>
`;
}).join("");
}
function renderActions(actions) {
const tbody = document.getElementById("action-rows");
const day = getDayNumber();
tbody.innerHTML = actions.map(a => {
let statusClass = a.status;
let statusLabel = a.status.replace("-", " ").replace(/\b\w/g, c => c.toUpperCase());
return `
<tr>
<td class="day-col">Day ${a.day}</td>
<td>${a.action}</td>
<td class="owner-col">${a.owner}</td>
<td class="status-col"><span class="status-tag ${statusClass}">${statusLabel}</span></td>
</tr>
`;
}).join("");
}
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 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 getNeeds() {
try {
const saved = localStorage.getItem("derez-needs");
if (saved) {
const parsed = JSON.parse(saved);
// Merge with defaults — keep done states
return NEEDS.map(def => {
const s = parsed.find(p => p.id === def.id);
return s ? { ...def, done: s.done } : def;
});
}
} catch(e) {}
return NEEDS;
}
/* ── Toggle sections ─────────────────────────── */
function toggleSection(id, header) {
const body = document.getElementById(id);
const toggle = header.querySelector(".section-toggle");
body.classList.toggle("hidden");
toggle.classList.toggle("collapsed");
}
/* ── Load data from JSON (if available) ───────── */
async function loadData() {
try {
const res = await fetch("data/launch-status.json?_=" + Date.now());
if (!res.ok) throw new Error("No data file yet");
const data = await res.json();
if (data.kpis) {
// Merge server KPIs with our defaults
const merged = KPIS.map(k => {
const s = data.kpis.find(d => d.id === k.id);
return s ? { ...k, current: s.current } : k;
});
renderKPIs(merged);
}
if (data.actions) {
const merged = ACTIONS.map(a => {
const s = data.actions.find(d => d.day === a.day && d.action === a.action);
return s ? { ...a, status: s.status } : a;
});
renderActions(merged);
}
if (data.updated) {
document.getElementById("last-updated").textContent =
"Updated: " + new Date(data.updated).toLocaleString("en-US", {
month: "short", day: "numeric", hour: "2-digit", minute: "2-digit"
});
}
} catch(e) {
// No data file yet — render defaults
renderKPIs(KPIS);
renderActions(ACTIONS);
}
}
/* ── Init ────────────────────────────────────── */
renderDay();
renderNeeds(getNeeds());
loadData();
</script>
</body>
</html>