<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LaunchOS · startup command center</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
:root {
--bg: #f6f8fc;
--surface: #ffffff;
--surface-2: #f0f3f9;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--primary: #4f46e5;
--primary-soft: #eef2ff;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--shadow: 0 8px 20px rgba(0, 0, 0, 0.04);
--radius: 16px;
--transition: 0.2s ease;
}
body.dark {
--bg: #0f172a;
--surface: #1e293b;
--surface-2: #273449;
--text: #e2e8f0;
--text-muted: #94a3b8;
--border: #334155;
--primary: #818cf8;
--primary-soft: #312e81;
--shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
}
body {
background: var(--bg);
color: var(--text);
display: flex;
min-height: 100vh;
transition: background 0.3s, color 0.3s;
}
/* sidebar */
.sidebar {
width: 260px;
background: var(--surface);
border-right: 1px solid var(--border);
padding: 24px 16px;
display: flex;
flex-direction: column;
gap: 8px;
position: sticky;
top: 0;
height: 100vh;
transition: background 0.3s;
}
.sidebar .brand {
font-weight: 700;
font-size: 1.4rem;
letter-spacing: -0.02em;
padding: 8px 12px 20px;
display: flex;
align-items: center;
gap: 8px;
color: var(--primary);
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 10px;
color: var(--text-muted);
font-weight: 500;
cursor: pointer;
transition: background 0.2s, color 0.2s;
border: none;
background: transparent;
font-size: 0.95rem;
width: 100%;
text-align: left;
}
.nav-item:hover {
background: var(--surface-2);
color: var(--text);
}
.nav-item.active {
background: var(--primary-soft);
color: var(--primary);
font-weight: 600;
}
.nav-item i {
width: 20px;
text-align: center;
}
.sidebar-footer {
margin-top: auto;
padding: 12px;
font-size: 0.8rem;
color: var(--text-muted);
border-top: 1px solid var(--border);
}
/* main */
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.topbar {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 12px 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
transition: background 0.3s;
}
.countdown-badge {
background: var(--primary-soft);
color: var(--primary);
padding: 6px 14px;
border-radius: 40px;
font-weight: 600;
font-size: 0.9rem;
}
.top-actions {
display: flex;
gap: 12px;
align-items: center;
}
.icon-btn {
background: var(--surface-2);
border: none;
border-radius: 10px;
padding: 8px 12px;
font-weight: 500;
cursor: pointer;
color: var(--text);
transition: 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
.icon-btn:hover {
background: var(--border);
}
.content {
padding: 28px 32px;
overflow-y: auto;
flex: 1;
}
/* cards & grid */
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 20px;
margin-bottom: 24px;
}
.card {
background: var(--surface);
border-radius: var(--radius);
padding: 20px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
transition: background 0.3s, border 0.3s;
}
.card h3 {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.metric {
font-size: 1.8rem;
font-weight: 700;
color: var(--primary);
}
.progress-bar {
height: 8px;
background: var(--surface-2);
border-radius: 20px;
overflow: hidden;
margin: 12px 0;
}
.progress-fill {
height: 100%;
background: var(--primary);
border-radius: 20px;
transition: width 0.4s;
}
.task-board {
display: flex;
gap: 16px;
overflow-x: auto;
padding-bottom: 12px;
}
.task-col {
background: var(--surface-2);
border-radius: 14px;
padding: 12px;
min-width: 200px;
flex: 1;
}
.task-col h4 {
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--text-muted);
margin-bottom: 12px;
}
.task-item {
background: var(--surface);
border-radius: 10px;
padding: 12px;
margin-bottom: 8px;
border: 1px solid var(--border);
cursor: grab;
font-size: 0.9rem;
box-shadow: 0 2px 6px rgba(0,0,0,0.02);
}
.task-item .priority {
font-size: 0.7rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 20px;
background: var(--primary-soft);
color: var(--primary);
display: inline-block;
margin-top: 6px;
}
.tag {
background: var(--primary-soft);
color: var(--primary);
padding: 2px 10px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
}
.btn {
background: var(--primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
.btn-outline {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
.btn-outline:hover {
background: var(--surface-2);
}
.btn:hover {
opacity: 0.9;
}
.timeline {
display: flex;
flex-direction: column;
gap: 16px;
}
.timeline-item {
display: flex;
gap: 16px;
align-items: flex-start;
}
.timeline-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--primary);
margin-top: 6px;
}
.timeline-content {
flex: 1;
background: var(--surface);
padding: 16px;
border-radius: 12px;
border: 1px solid var(--border);
}
.note-input, .copy-area {
width: 100%;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 12px;
padding: 14px;
color: var(--text);
font-size: 0.95rem;
resize: vertical;
margin-bottom: 12px;
}
.hidden { display: none; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.marketing-channel {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.status-badge {
background: var(--surface-2);
padding: 4px 10px;
border-radius: 20px;
font-size: 0.75rem;
}
@media (max-width: 800px) {
.sidebar { width: 70px; padding: 16px 8px; }
.sidebar .brand span, .sidebar .nav-item span { display: none; }
.sidebar .brand { justify-content: center; }
.content { padding: 20px; }
}
</style>
</head>
<body>
<aside class="sidebar" id="sidebar">
<div class="brand">🚀 LaunchOS</div>
<button class="nav-item active" data-view="dashboard"><i>📊</i> <span>Dashboard</span></button>
<button class="nav-item" data-view="product"><i>📦</i> <span>Product</span></button>
<button class="nav-item" data-view="tasks"><i>✅</i> <span>Tasks</span></button>
<button class="nav-item" data-view="launch"><i>📅</i> <span>Launch Plan</span></button>
<button class="nav-item" data-view="copy"><i>✍️</i> <span>Copy</span></button>
<button class="nav-item" data-view="marketing"><i>📣</i> <span>Marketing</span></button>
<button class="nav-item" data-view="metrics"><i>📈</i> <span>Metrics</span></button>
<button class="nav-item" data-view="notes"><i>📝</i> <span>Notes</span></button>
<div class="sidebar-footer">v1.0 · local</div>
</aside>
<div class="main">
<div class="topbar">
<div class="countdown-badge" id="countdownDisplay">⏳ launch in 12d</div>
<div class="top-actions">
<button class="icon-btn" id="themeToggle">🌓</button>
<button class="icon-btn" id="exportBtn">⬇️ export</button>
<button class="icon-btn" id="importBtn">⬆️ import</button>
</div>
</div>
<div class="content" id="appContent">
<!-- dynamic content injected -->
</div>
</div>
<script>
(function(){
// ---------- STATE ----------
const defaultState = {
product: {
name: 'NovaBoard',
tagline: 'AI-powered team alignment',
audience: 'Startup founders & product teams',
problem: 'Scattered tools and unclear priorities',
features: ['Unified roadmap', 'AI task prioritization', 'Launch checklist'],
revenueModel: 'Subscription (SaaS)',
launchDate: new Date(Date.now() + 12*86400000).toISOString().slice(0,10)
},
tasks: [
{ id: 1, title: 'Finalize product spec', desc: 'Core features locked', priority: 'high', status: 'done', assignee: 'AI', due: '2025-05-20' },
{ id: 2, title: 'Design landing page', desc: 'Hero section + copy', priority: 'medium', status: 'inprogress', assignee: 'Human', due: '2025-05-25' },
{ id: 3, title: 'Set up analytics', desc: 'GA4 + Mixpanel', priority: 'medium', status: 'todo', assignee: 'AI', due: '2025-06-01' },
{ id: 4, title: 'Draft email sequence', desc: '3 emails for launch', priority: 'high', status: 'review', assignee: 'AI', due: '2025-05-28' },
],
milestones: [
{ phase: 'Product Development', tasks: ['Build MVP', 'Internal testing'], done: 1 },
{ phase: 'Marketing Prep', tasks: ['Brand assets', 'Social accounts'], done: 0 },
{ phase: 'Pre-Launch', tasks: ['Waitlist page', 'Press kit'], done: 0 },
{ phase: 'Launch Day', tasks: ['Product Hunt', 'Email blast'], done: 0 },
{ phase: 'Post-Launch', tasks: ['User feedback', 'Iterate'], done: 0 }
],
marketing: [
{ channel: 'Twitter / X', status: 'inprogress', note: 'Weekly threads' },
{ channel: 'LinkedIn', status: 'todo', note: 'Founder posts' },
{ channel: 'Email', status: 'todo', note: 'Sequence ready' },
{ channel: 'Ads', status: 'notstarted', note: 'Budget TBD' }
],
metrics: { revenue: 12000, users: 340, signups: 89, engagement: 4.2 },
notes: [
{ id: 1, text: 'Idea: partner with micro-influencers', tag: 'Marketing' },
{ id: 2, text: 'Use case: onboarding flow simplification', tag: 'Product' }
],
copyBlocks: {
description: 'NovaBoard aligns your team with AI-driven priorities and launch checklists.',
tagline: 'Ship faster, together.',
pitch: 'NovaBoard turns your product chaos into a clear launch path.',
email: 'Hey {{name}}, ready to launch?',
social: '🚀 We are building the future of team alignment.'
}
};
let state = JSON.parse(localStorage.getItem('launchOS_state')) || defaultState;
let currentView = 'dashboard';
// ---------- HELPERS ----------
function saveState() {
localStorage.setItem('launchOS_state', JSON.stringify(state));
}
function getCountdown() {
const launch = new Date(state.product.launchDate);
const now = new Date();
const diff = Math.ceil((launch - now) / (1000*60*60*24));
return diff > 0 ? `${diff}d` : 'launched';
}
function updateCountdown() {
document.getElementById('countdownDisplay').innerText = `⏳ launch in ${getCountdown()}`;
}
// ---------- RENDER ----------
function render() {
const app = document.getElementById('appContent');
let html = '';
if (currentView === 'dashboard') html = renderDashboard();
else if (currentView === 'product') html = renderProduct();
else if (currentView === 'tasks') html = renderTasks();
else if (currentView === 'launch') html = renderLaunch();
else if (currentView === 'copy') html = renderCopy();
else if (currentView === 'marketing') html = renderMarketing();
else if (currentView === 'metrics') html = renderMetrics();
else if (currentView === 'notes') html = renderNotes();
app.innerHTML = html;
updateCountdown();
attachEvents();
}
function renderDashboard() {
const totalTasks = state.tasks.length;
const done = state.tasks.filter(t => t.status === 'done').length;
const readiness = Math.round((done / totalTasks) * 100);
const recent = state.notes.slice(-2).map(n => `<div class="flex-between"><span>📌 ${n.text}</span><span class="tag">${n.tag}</span></div>`).join('');
return `
<h2 style="margin-bottom:20px">Startup Dashboard</h2>
<div class="grid-2">
<div class="card"><h3>Launch countdown</h3><div class="metric">${getCountdown()}</div><div class="progress-bar"><div class="progress-fill" style="width:${Math.min(100, readiness)}%"></div></div></div>
<div class="card"><h3>Tasks done</h3><div class="metric">${done}/${totalTasks}</div><div>readiness ${readiness}%</div></div>
<div class="card"><h3>Revenue goal</h3><div class="metric">$${state.metrics.revenue.toLocaleString()}</div><div>users: ${state.metrics.users}</div></div>
<div class="card"><h3>Recent activity</h3>${recent || '<span class="text-muted">no notes yet</span>'}</div>
</div>
<div class="card">
<h3>Quick add</h3>
<div style="display:flex; gap:8px; flex-wrap:wrap">
<button class="btn" onclick="quickAddTask()">+ Task</button>
<button class="btn btn-outline" onclick="quickNote()">+ Note</button>
<button class="btn btn-outline" onclick="quickMilestone()">+ Milestone</button>
</div>
</div>
`;
}
function renderProduct() {
const p = state.product;
return `
<h2 style="margin-bottom:20px">Product Definition</h2>
<div class="card">
<div class="flex-between"><h3>Product brief</h3><button class="btn btn-outline" onclick="alert('copied to clipboard (simulated)')">📋 share</button></div>
<p><strong>Name:</strong> ${p.name}</p>
<p><strong>Tagline:</strong> ${p.tagline}</p>
<p><strong>Audience:</strong> ${p.audience}</p>
<p><strong>Problem:</strong> ${p.problem}</p>
<p><strong>Features:</strong> ${p.features.join(', ')}</p>
<p><strong>Revenue model:</strong> ${p.revenueModel}</p>
<p><strong>Launch date:</strong> ${p.launchDate}</p>
<hr style="margin:16px 0">
<button class="btn" onclick="editProduct()">✏️ Edit fields</button>
</div>
`;
}
function renderTasks() {
const cols = ['todo','inprogress','review','done'];
const labels = {todo:'To Do', inprogress:'In Progress', review:'Review', done:'Done'};
let boardHtml = '<div class="task-board">';
cols.forEach(col => {
const tasks = state.tasks.filter(t => t.status === col);
boardHtml += `<div class="task-col"><h4>${labels[col]}</h4>`;
tasks.forEach(t => {
boardHtml += `<div class="task-item" draggable="true" data-id="${t.id}" data-status="${col}">
<strong>${t.title}</strong><br><small>${t.desc}</small>
<div><span class="priority">${t.priority}</span> <span class="tag">${t.assignee}</span></div>
</div>`;
});
boardHtml += `</div>`;
});
boardHtml += '</div>';
return `<h2 style="margin-bottom:20px">Task Board</h2>${boardHtml}
<button class="btn" style="margin-top:16px" onclick="addManualTask()">+ Add custom task</button>`;
}
function renderLaunch() {
let tl = '<div class="timeline">';
state.milestones.forEach((m, idx) => {
tl += `<div class="timeline-item"><div class="timeline-dot"></div><div class="timeline-content">
<h4>${m.phase}</h4><p>${m.tasks.join(' · ')}</p>
<span class="tag">${m.done ? '✓ done' : 'pending'}</span>
</div></div>`;
});
tl += '</div>';
return `<h2 style="margin-bottom:20px">Launch Timeline</h2>${tl}`;
}
function renderCopy() {
const c = state.copyBlocks;
return `
<h2 style="margin-bottom:20px">Copy & Content</h2>
<div class="card">
<h3>Tagline</h3><textarea class="copy-area" id="copyTagline">${c.tagline}</textarea>
<h3>Description</h3><textarea class="copy-area" id="copyDesc">${c.description}</textarea>
<h3>Elevator pitch</h3><textarea class="copy-area" id="copyPitch">${c.pitch}</textarea>
<h3>Email</h3><textarea class="copy-area" id="copyEmail">${c.email}</textarea>
<h3>Social post</h3><textarea class="copy-area" id="copySocial">${c.social}</textarea>
<button class="btn" onclick="saveCopy()">💾 save copy</button>
<button class="btn btn-outline" onclick="exportCopyText()">⬇️ export .txt</button>
</div>
`;
}
function renderMarketing() {
let items = state.marketing.map((m,i) => `
<div class="marketing-channel">
<div><strong>${m.channel}</strong><br><small>${m.note}</small></div>
<select onchange="updateMarketingStatus(${i}, this.value)" class="status-badge">
<option ${m.status==='notstarted'?'selected':''}>notstarted</option>
<option ${m.status==='inprogress'?'selected':''}>inprogress</option>
<option ${m.status==='done'?'selected':''}>done</option>
</select>
</div>`).join('');
return `<h2 style="margin-bottom:20px">Marketing & Outreach</h2><div class="card">${items}</div>`;
}
function renderMetrics() {
const m = state.metrics;
return `
<h2 style="margin-bottom:20px">Metrics & Goals</h2>
<div class="grid-2">
<div class="card"><h3>Revenue</h3><div class="metric">$${m.revenue}</div><input type="number" id="metricRevenue" value="${m.revenue}" style="width:100%"></div>
<div class="card"><h3>Users</h3><div class="metric">${m.users}</div><input type="number" id="metricUsers" value="${m.users}" style="width:100%"></div>
<div class="card"><h3>Sign-ups</h3><div class="metric">${m.signups}</div><input type="number" id="metricSignups" value="${m.signups}" style="width:100%"></div>
<div class="card"><h3>Engagement</h3><div class="metric">${m.engagement}</div><input type="number" step="0.1" id="metricEngagement" value="${m.engagement}" style="width:100%"></div>
</div>
<button class="btn" onclick="updateMetrics()">update metrics</button>
`;
}
function renderNotes() {
let notesHtml = state.notes.map(n => `<div class="flex-between" style="padding:8px 0"><span>${n.text}</span><span class="tag">${n.tag}</span></div>`).join('');
return `
<h2 style="margin-bottom:20px">Notes & Brainstorming</h2>
<div class="card">
<input class="note-input" id="newNoteText" placeholder="Write a note...">
<select id="newNoteTag"><option>Product</option><option>Marketing</option><option>Launch</option><option>Operations</option></select>
<button class="btn" onclick="addNote()">+ Add note</button>
<hr style="margin:16px 0">
${notesHtml}
</div>
`;
}
// ---------- EVENTS & ACTIONS ----------
function attachEvents() {
document.querySelectorAll('.nav-item').forEach(btn => {
btn.onclick = () => {
document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentView = btn.dataset.view;
render();
};
});
// drag & drop
document.querySelectorAll('.task-item').forEach(item => {
item.addEventListener('dragstart', e => {
e.dataTransfer.setData('text/plain', item.dataset.id);
});
});
document.querySelectorAll('.task-col').forEach(col => {
col.addEventListener('dragover', e => e.preventDefault());
col.addEventListener('drop', e => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
const newStatus = col.querySelector('h4').innerText.toLowerCase().replace(' ', '');
const task = state.tasks.find(t => t.id == id);
if (task) {
task.status = newStatus;
saveState(); render();
}
});
});
}
// global functions
window.quickAddTask = function() {
const title = prompt('Task title');
if (title) {
state.tasks.push({ id: Date.now(), title, desc: 'quick task', priority: 'medium', status: 'todo', assignee: 'AI', due: '2025-06-01' });
saveState(); render();
}
};
window.quickNote = function() {
const text = prompt('Note:');
if (text) { state.notes.push({ id: Date.now(), text, tag: 'Launch' }); saveState(); render(); }
};
window.quickMilestone = function() {
const phase = prompt('Milestone phase');
if (phase) { state.milestones.push({ phase, tasks: ['new task'], done: 0 }); saveState(); render(); }
};
window.editProduct = function() {
const newName = prompt('Product name', state.product.name);
if (newName) { state.product.name = newName; saveState(); render(); }
};
window.addManualTask = function() {
const title = prompt('Task title');
if (title) { state.tasks.push({ id: Date.now(), title, desc: 'custom', priority: 'low', status: 'todo', assignee: 'Human', due: '2025-06-10' }); saveState(); render(); }
};
window.saveCopy = function() {
state.copyBlocks.tagline = document.getElementById('copyTagline').value;
state.copyBlocks.description = document.getElementById('copyDesc').value;
state.copyBlocks.pitch = document.getElementById('copyPitch').value;
state.copyBlocks.email = document.getElementById('copyEmail').value;
state.copyBlocks.social = document.getElementById('copySocial').value;
saveState(); alert('Copy saved');
};
window.exportCopyText = function() {
const text = Object.values(state.copyBlocks).join('\n\n');
const blob = new Blob([text], {type: 'text/plain'});
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'copy.txt'; a.click();
};
window.updateMarketingStatus = function(idx, val) {
state.marketing[idx].status = val;
saveState(); render();
};
window.updateMetrics = function() {
state.metrics.revenue = +document.getElementById('metricRevenue').value;
state.metrics.users = +document.getElementById('metricUsers').value;
state.metrics.signups = +document.getElementById('metricSignups').value;
state.metrics.engagement = +document.getElementById('metricEngagement').value;
saveState(); render();
};
window.addNote = function() {
const text = document.getElementById('newNoteText').value;
const tag = document.getElementById('newNoteTag').value;
if (text) { state.notes.push({ id: Date.now(), text, tag }); saveState(); render(); }
};
// theme
document.getElementById('themeToggle').onclick = () => {
document.body.classList.toggle('dark');
};
// export/import
document.getElementById('exportBtn').onclick = () => {
const blob = new Blob([JSON.stringify(state, null, 2)], {type: 'application/json'});
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'launchos-backup.json'; a.click();
};
document.getElementById('importBtn').onclick = () => {
const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json';
input.onchange = e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => {
try {
state = JSON.parse(ev.target.result);
saveState(); render();
} catch (err) { alert('Invalid JSON'); }
};
reader.readAsText(file);
};
input.click();
};
// init
render();
})();
</script>
</body>
</html>
Discusión
0 comentarios