prospector/designs/control.html
Natalie 0a5af348dd feat(prospector): align design contract with PWA-only architecture
Add the three missing first-class design prototypes and retire the stale
iOS/OSX-SSO framing so designs/ (the authoritative visual+behavior contract)
matches the current single-PWA reality.

- add designs/campaigns.html (facets/preview/launch; core prospecting feature)
- add designs/markets.html (tour-market stats: peak hours/days, conversion)
- add designs/control.html (GO/PAUSE/AWAY kill-switch, digest, activity, held)
- rewrite designs/index.html: PWA-only hub, drop iOS/OSX/SSO, link all 9 designs
- remove designs/ios-prospector-tab.html (Swift target dropped in bcbd558)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:02:16 -04:00

222 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quinn Prospector • Control</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
:root {
--app-font: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
}
body {
font-family: var(--app-font);
font-feature-settings: "kern" "liga" "tnum";
}
.mac-window {
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.6), 0 10px 10px -5px rgb(0 0 0 / 0.4);
border: 1px solid #3f3f46;
}
.text-primary { color: #f1f5f9; }
.text-muted { color: #94a3b8; }
.panel { background: #18181b; border: 1px solid #3f3f46; border-radius: 12px; padding: 14px; }
.panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.panel__title { font-size: 13px; font-weight: 600; color: #f1f5f9; }
.panel__meta { font-size: 10px; color: #64748b; font-family: ui-monospace, monospace; }
/* mode kill-switch — mirror web .mode-seg / .mode-btn */
.mode-seg { display: flex; gap: 8px; }
.mode-btn { flex: 1; padding: 12px; border-radius: 10px; font-size: 15px; font-weight: 700; background: #27272a; color: #94a3b8; border: 1px solid #3f3f46; cursor: pointer; transition: all .1s ease; }
.mode-btn:hover { background: #3f3f46; }
.mode-btn--active.mode-btn--go { background: #064e3b; border-color: #10b981; color: #6ee7b7; }
.mode-btn--active.mode-btn--pause { background: #78350f; border-color: #f59e0b; color: #fcd34d; }
.mode-btn--active.mode-btn--away { background: #1e3a5f; border-color: #3b82f6; color: #93c5fd; }
/* digest pills */
.pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pill { font-size: 11px; padding: 3px 10px; border-radius: 9999px; border: 1px solid #3f3f46; background: #27272a; }
.pill--send { background: #064e3b; border-color: #10b981; color: #6ee7b7; }
.pill--hold { background: #78350f; border-color: #f59e0b; color: #fcd34d; }
.pill--err { background: #7f1d1d; border-color: #ef4444; color: #fca5a5; }
.pill--ok { background: #064e3b; border-color: #10b981; color: #6ee7b7; }
.pill--bad { background: #7f1d1d; border-color: #ef4444; color: #fca5a5; }
/* activity terminal */
.terminal { background: #0a0a0c; border: 1px solid #27272a; border-radius: 8px; padding: 8px; height: 180px; overflow: auto; font-family: ui-monospace, monospace; }
.logrow { display: flex; align-items: center; gap: 8px; font-size: 11px; padding: 2px 0; }
.logrow__time { color: #64748b; }
.badge { font-size: 9px; font-weight: 700; padding: 1px 6px; border-radius: 4px; }
.badge--send { background: #064e3b; color: #6ee7b7; }
.badge--hold { background: #78350f; color: #fcd34d; }
.logrow__handle { color: #e2e8f0; font-weight: 500; }
.logrow__class { color: #93c5fd; }
.logrow__reason { color: #fca5a5; }
/* held queue rows */
.held { background: #27272a; border: 1px solid #3f3f46; border-radius: 8px; padding: 8px 10px; margin-bottom: 6px; }
.held__top { display: flex; justify-content: space-between; }
.held__handle { font-weight: 500; font-size: 12px; }
.held__time { font-size: 10px; color: #64748b; }
.held__meta { display: flex; align-items: center; gap: 8px; margin-top: 3px; }
.tag { background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 0 7px; font-size: 10px; color: #93c5fd; }
.held__reason { font-size: 11px; color: #fca5a5; }
</style>
</head>
<body class="bg-[#0a0a0c] flex items-center justify-center min-h-screen p-6 text-[#e2e8f0]">
<div class="mac-window w-[1100px] bg-[#1c1c1f] border border-[#3f3f46] rounded-2xl overflow-hidden shadow-2xl flex flex-col" style="height: 760px;">
<!-- macOS Title Bar -->
<div class="h-9 bg-[#27272a] border-b border-[#3f3f46] flex items-center px-3 gap-x-2 flex-shrink-0">
<div class="flex gap-x-1.5">
<div class="w-[12px] h-[12px] rounded-full bg-[#ff5f57] border border-[#e0443e]"></div>
<div class="w-[12px] h-[12px] rounded-full bg-[#febc2e] border border-[#d9a023]"></div>
<div class="w-[12px] h-[12px] rounded-full bg-[#28c840] border border-[#1a9e2b]"></div>
</div>
<div class="flex-1 text-center">
<span class="text-sm font-medium text-primary">Quinn Prospector — Control</span>
</div>
<div class="flex items-center gap-x-2 text-[10px]">
<div id="mode-chip" class="px-2 py-px bg-emerald-900/60 text-emerald-400 rounded text-[9px] font-mono flex items-center gap-x-1">
<i class="fa-solid fa-circle fa-2xs"></i><span>runner: GO</span>
</div>
</div>
</div>
<div class="h-9 bg-[#18181b] border-b border-[#3f3f46] px-4 flex items-center gap-x-3 text-xs flex-shrink-0">
<a href="index.html" class="text-muted hover:text-white"><i class="fa-solid fa-arrow-left"></i> Hub</a>
<span class="text-muted">Kill-switch + digest + live activity + held queue. The operator's monitoring cockpit.</span>
</div>
<div class="flex-1 overflow-auto p-4">
<div class="grid grid-cols-2 gap-4">
<!-- PWA install card -->
<section class="panel col-span-2" style="background:#18181b">
<div class="flex items-center justify-between">
<div>
<div class="text-xs font-semibold mb-1">PWA / Chrome App</div>
<div class="text-[10px] text-muted">One installable Chrome PWA, served same-origin under <span class="font-mono text-emerald-400">/prospector/*</span>. Install for a standalone window — no URL bar, full F12 devtools. (beforeinstallprompt captured; manifest in public/)</div>
</div>
<button onclick="installPwa()" class="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-medium whitespace-nowrap flex items-center gap-x-2">
<i class="fa-solid fa-download"></i> Install as Chrome App
</button>
</div>
<div id="install-done" class="text-emerald-400 text-xs mt-2 hidden">✓ Installed — launch from Chrome apps or desktop shortcut.</div>
</section>
<!-- Mode control / kill-switch -->
<section class="panel">
<div class="panel__head">
<span class="panel__title">Runner Mode</span>
<span class="panel__meta" id="mode-updated">updated just now</span>
</div>
<div class="mode-seg" id="mode-seg">
<button class="mode-btn mode-btn--active mode-btn--go" data-mode="GO" onclick="setMode('GO')">GO</button>
<button class="mode-btn" data-mode="PAUSE" onclick="setMode('PAUSE')">PAUSE</button>
<button class="mode-btn" data-mode="AWAY" onclick="setMode('AWAY')">AWAY</button>
</div>
<div class="text-xs text-muted mt-3">
Current: <strong id="mode-current" class="text-emerald-400">GO</strong> · engine <span class="font-mono">deepseek-r1-distill</span>
</div>
<div class="text-[10px] text-muted mt-1">GO = auto-qualify + auto-send (cap-aware). PAUSE = drafts only, no sends. AWAY = everything held for review.</div>
</section>
<!-- Digest -->
<section class="panel">
<div class="panel__head">
<span class="panel__title">Digest</span>
<span class="panel__meta">last 12h</span>
</div>
<div class="pills">
<span class="pill pill--send">sent 14</span>
<span class="pill pill--hold">held 3</span>
<span class="pill pill--err">errors 0</span>
<span class="pill pill--ok">Mac reachable: yes</span>
</div>
<div class="text-[10px] text-muted mt-3">Rolling 12h digest from the runner. Mac reachability = mac-sync mesh up (Apple Notes pastebin, outbox, messages).</div>
</section>
<!-- Activity feed -->
<section class="panel">
<div class="panel__head">
<span class="panel__title">Activity Feed</span>
<span class="panel__meta" id="activity-count">6 recent</span>
</div>
<div class="terminal" id="terminal"></div>
</section>
<!-- Held queue -->
<section class="panel">
<div class="panel__head">
<span class="panel__title">Held Queue</span>
<span class="panel__meta" id="held-count">3 held</span>
</div>
<div id="held-list"></div>
</section>
</div>
</div>
<div class="h-6 bg-[#18181b] border-t border-[#3f3f46] px-4 flex items-center text-[10px] text-muted font-mono flex-shrink-0">
Settings + activity + held-queue poll /prospector/* same-origin. Mode change = PUT /prospector/settings.
</div>
</div>
<script>
const activity = [
{ id: 'a1', time: '14:31', outcome: 'send', handle: '+14125551234', classification: 'qualified', holdReason: null },
{ id: 'a2', time: '14:29', outcome: 'hold', handle: '+13125551234', classification: 'content-curious', holdReason: 'ambiguous intent' },
{ id: 'a3', time: '14:22', outcome: 'send', handle: '+521234567890', classification: 'work:dates', holdReason: null },
{ id: 'a4', time: '14:18', outcome: 'send', handle: '+14125555678', classification: 'qualified', holdReason: null },
{ id: 'a5', time: '14:10', outcome: 'hold', handle: '+12125554321', classification: 'harvester', holdReason: 'low MR / safety screen' },
{ id: 'a6', time: '14:02', outcome: 'send', handle: '+14155559012', classification: 'qualified', holdReason: null },
];
const held = [
{ id: 'h1', handle: '+13125551234', time: '14:29', classification: 'content-curious', holdReason: 'ambiguous intent — human review' },
{ id: 'h2', handle: '+12125554321', time: '14:10', classification: 'harvester', holdReason: 'low MR / safety screen' },
{ id: 'h3', handle: '+14085550001', time: '13:51', classification: null, holdReason: 'AWAY window — all inbound held' },
];
const modeChipClass = { GO: 'bg-emerald-900/60 text-emerald-400', PAUSE: 'bg-amber-900/60 text-amber-400', AWAY: 'bg-blue-900/60 text-blue-400' };
function setMode(next) {
if ((next === 'GO' || next === 'AWAY') && !confirm(`Switch runner to ${next}?`)) return;
document.querySelectorAll('#mode-seg .mode-btn').forEach((b) => {
const on = b.dataset.mode === next;
b.className = 'mode-btn' + (on ? ` mode-btn--active mode-btn--${next.toLowerCase()}` : '');
});
const cur = document.getElementById('mode-current');
cur.textContent = next;
cur.className = next === 'GO' ? 'text-emerald-400' : next === 'PAUSE' ? 'text-amber-400' : 'text-blue-400';
document.getElementById('mode-updated').textContent = 'updated just now';
const chip = document.getElementById('mode-chip');
chip.className = `px-2 py-px ${modeChipClass[next]} rounded text-[9px] font-mono flex items-center gap-x-1`;
chip.innerHTML = `<i class="fa-solid fa-circle fa-2xs"></i><span>runner: ${next}</span>`;
}
function installPwa() {
alert('Use Chrome menu (⋮) → Install Prospector, or "Create shortcut" → "Open as window" for a standalone PWA (full devtools, no chrome). Matches the designs/ PWA spec.');
document.getElementById('install-done').classList.remove('hidden');
}
function renderActivity() {
const t = document.getElementById('terminal');
t.innerHTML = activity.map((i) => `
<div class="logrow">
<span class="logrow__time">${i.time}</span>
<span class="badge badge--${i.outcome}">${i.outcome}</span>
<span class="logrow__handle">${i.handle}</span>
${i.classification ? `<span class="logrow__class">${i.classification}</span>` : ''}
${i.holdReason ? `<span class="logrow__reason">${i.holdReason}</span>` : ''}
</div>`).join('');
document.getElementById('activity-count').textContent = `${activity.length} recent`;
}
function renderHeld() {
const el = document.getElementById('held-list');
el.innerHTML = held.map((i) => `
<div class="held">
<div class="held__top"><span class="held__handle">${i.handle}</span><span class="held__time">${i.time}</span></div>
<div class="held__meta">${i.classification ? `<span class="tag">${i.classification}</span>` : ''}<span class="held__reason">${i.holdReason}</span></div>
</div>`).join('');
document.getElementById('held-count').textContent = `${held.length} held`;
}
window.onload = function () { renderActivity(); renderHeld(); };
</script>
</body>
</html>