diff --git a/tests/test_app.py b/tests/test_app.py
index cf0cecf..67d9ccc 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -3,6 +3,7 @@ import os
import tempfile
import threading
import time
+from pathlib import Path
import urllib.error
import unittest
from http.client import HTTPConnection
@@ -191,6 +192,13 @@ class BrowserOnlyAppTests(unittest.TestCase):
saved = server.db().execute("SELECT ai_probability,ai_recommendation,ai_reasoning FROM prospects WHERE id=1").fetchone()
self.assertEqual(tuple(saved), (84.0, "contact", "Active business with a clear website opportunity."))
+ def test_prospect_workspace_has_pipeline_and_view_switcher(self):
+ script = Path(__file__).resolve().parents[1].joinpath("web", "app.js").read_text()
+ self.assertIn("prospectBoard", script)
+ self.assertIn("prospectViewMode", script)
+ self.assertIn("data-board-status", script)
+ self.assertIn("Needs review", script)
+
def test_firecrawl_search_normalises_public_results(self):
response = {"success": True, "data": {"web": [{"title": "Cape Town Plumbing", "url": "https://plumber.example.co.za/", "description": "Local plumbing services"}]}}
with patch("api.server.http_json", return_value=response) as request, patch("api.server._safe_url", return_value=True):
diff --git a/web/app.js b/web/app.js
index 9cf7b14..7799bc3 100644
--- a/web/app.js
+++ b/web/app.js
@@ -10,7 +10,7 @@ let prospectScoreBand='all',prospectWebsiteFilter='all',prospectEmailFilter='all
const HIGH_POTENTIAL_SCORE=70;
async function api(path,opt={}){const headers={'Content-Type':'application/json',...(opt.headers||{})};if(csrfToken&&!headers['X-CSRF-Token'])headers['X-CSRF-Token']=csrfToken;const r=await fetch(path,{...opt,credentials:'same-origin',headers});const b=await r.json().catch(()=>({}));if(r.status===401&&!path.startsWith('/api/auth/')){currentUser=null;csrfToken='';renderAuth({setup_required:false});}if(!r.ok)throw Error(b.error||`Request failed (${r.status})`);return b}
let loadWarnings=[];
-async function refreshWorkerStatus(){const dot=$('sourceDot'),label=$('sourceLabel'),sub=$('sourceSub');if(!dot||!label||!sub)return;try{const health=await api('/api/health');const ready=Boolean(health.configured);dot.classList.toggle('offline',!ready);label.textContent=health.source==='browser'?'Browser discovery':'Google Maps worker';sub.textContent=ready?'Ready':'Not configured';}catch(error){dot.classList.add('offline');label.textContent='Discovery status';sub.textContent='Unavailable';}}
+async function refreshWorkerStatus(){const dot=$('sourceDot'),label=$('sourceLabel'),sub=$('sourceSub');if(!dot||!label||!sub)return;try{const health=await api('/api/health');const ready=Boolean(health.configured);dot.classList.toggle('offline',!ready);label.textContent=health.source==='browser'?'Browser discovery':health.source==='firecrawl'?'Firecrawl web search':'Google Maps worker';sub.textContent=ready?'Ready':'Not configured';}catch(error){dot.classList.add('offline');label.textContent='Discovery status';sub.textContent='Unavailable';}}
function date(v){if(!v)return'—';const d=new Date(v);return isNaN(d)?String(v):d.toLocaleString(undefined,{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})}
function layout(title,sub,body,actions=''){ $('pageName').textContent=title; return `${body}` }
function empty(text){return `
⌕ ${text}
`}
@@ -22,7 +22,7 @@ function prospectTabs(){const categories=[...new Set(prospects.map(prospectCateg
function prospectRows(){const q=($('prospectSearch')?.value||'').toLowerCase();return prospects.filter(p=>{const hay=[p.name,p.domain,p.search_category,p.category,p.address,p.emails].join(' ').toLowerCase();const score=effectiveScore(p);const websiteStatus=String(p.website_status||'unknown').toLowerCase();const tabMatch=activeProspectCategory==='all'?score>=HIGH_POTENTIAL_SCORE:prospectCategory(p)===activeProspectCategory;const scoreMatch=prospectScoreBand==='all'||(prospectScoreBand==='hot'&&score>=85)||(prospectScoreBand==='high'&&score>=70&&score<85)||(prospectScoreBand==='medium'&&score>=45&&score<70)||(prospectScoreBand==='low'&&score<45);const websiteMatch=prospectWebsiteFilter==='all'||(prospectWebsiteFilter==='missing'&&(!p.website||websiteStatus==='no_detected_website'))||(prospectWebsiteFilter==='broken'&&['broken','unreachable','error','timeout'].some(v=>websiteStatus.includes(v)))||(prospectWebsiteFilter==='healthy'&&websiteStatus==='healthy');const emailMatch=prospectEmailFilter==='all'||(prospectEmailFilter==='has_email'&&Boolean(String(p.emails||'').trim()))||(prospectEmailFilter==='no_email'&&!String(p.emails||'').trim());const reviewMatch=prospectReviewFilter==='all'||String(p.review_status||'pending')===prospectReviewFilter;return tabMatch&&scoreMatch&&websiteMatch&&emailMatch&&reviewMatch&&(!q||hay.includes(q))}).sort((a,b)=>effectiveScore(b)-effectiveScore(a)||String(a.name||'').localeCompare(String(b.name||'')))}
function prospectTable(){const rows=prospectRows();return `Prospect Website Email Potential ${rows.map(p=>{const score=effectiveScore(p);return `${esc(p.name)} ${esc(prospectCategory(p))} · ${esc(p.address||'')} ${esc((p.website_status||'unknown').replaceAll('_',' '))} ${p.website?`Open ↗ `:'No detected website '}${p.emails?`✉ ${esc(p.emails)} `:'Not found '} ${score} ${score>=85?'Hot':score>=70?'High':score>=45?'Medium':'Low'}${p.ai_checked_at?' · AI':''} ✦ `}).join('')}
${rows.length?'':empty('No prospects match this tab and filter')}`}
function renderDashboard(){const total=prospects.length,pending=prospects.filter(p=>p.review_status==='pending').length,hot=prospects.filter(p=>effectiveScore(p)>=75).length,withEmail=prospects.filter(p=>p.emails).length;const strongest=[...prospects].sort((a,b)=>effectiveScore(b)-effectiveScore(a)).slice(0,5);$('view').innerHTML=layout('Dashboard','A clear view of what was found, what needs review, and where the best opportunities are.',`CATALOGUE ${total} saved prospects
NEEDS REVIEW ${pending} awaiting your decision
HIGH OPPORTUNITY ${hot} score 75 or higher
PUBLIC EMAILS ${withEmail} evidence-backed contacts
NEXT ACTION Review your strongest leads Inspect the evidence before deciding whether to contact them.
Open explorer → ${strongest.map(p=>{const score=effectiveScore(p);return `${score} ${esc(p.name)} ${esc(p.website_status?.replaceAll('_',' ')||'unknown')} · ${esc(p.emails||'no public email')}${p.ai_checked_at?' · AI assessed':''} › `}).join('')||empty('Run a discovery to build your catalogue')}
${runs.slice(0,6).map(r=>`
#${r.id} · ${esc(r.query)} ${esc(r.status)} · ${r.added||0} added ${date(r.created_at)}
`).join('')||empty('No runs yet')}
`)}
-function renderDiscover(){ $('view').innerHTML=layout('Discover','Set the criteria for this run. Source configuration and provider settings live in Settings.',`NEW RUN Find potential customers Search Google Maps, deduplicate the catalogue, then enrich each result.
Email collection always on HOW IT WORKS
Evidence-first pipeline Search Maps worker finds public business listings. Enrich Website, HTTPS, DNS hints and public email checks run safely. Review You decide what is relevant before any contact. `)}
+function renderDiscover(){ $('view').innerHTML=layout('Discover','Set the criteria for this run. Source configuration and provider settings live in Settings.',`NEW RUN Find potential customers Search your selected discovery source, verify public websites, and review the results before contact.
Email collection always on HOW IT WORKS
Evidence-first pipeline Search Maps worker finds public business listings. Enrich Website, HTTPS, DNS hints and public email checks run safely. Review You decide what is relevant before any contact. `)}
function renderProspects(){ $('view').innerHTML=layout('Prospects','Browse the strongest opportunities first, then narrow the catalogue with filters and sort by the latest score.',`${prospectTabs()}
Click any prospect to review evidence, change its status, assess it with AI, or draft a tailored message. Review status is changed inside the prospect drawer.
${prospectTable()}
`);if($('prospectScoreBand'))$('prospectScoreBand').value=prospectScoreBand;if($('prospectWebsiteFilter'))$('prospectWebsiteFilter').value=prospectWebsiteFilter;if($('prospectEmailFilter'))$('prospectEmailFilter').value=prospectEmailFilter;if($('prospectReviewFilter'))$('prospectReviewFilter').value=prospectReviewFilter}
function renderRuns(){ $('view').innerHTML=layout('Runs','Monitor discovery jobs, recover interrupted work, and inspect every state transition.',`Run Status Results Started Source Actions ${runs.map(r=>{const active=['queued','running'].includes(r.status);const retry=['failed','cancelled'].includes(r.status)||r.recovery_state==='retryable';return `#${r.id} ${esc(r.query)} ${esc(r.status)} ${esc(r.stage||'')}${r.recovery_state?` · ${esc(r.recovery_state)}`:''} ${r.added||0} added / ${r.found||0} found ${date(r.created_at)} ${esc(r.source||'google_maps')} ${active?`Cancel `:''}${retry?`Retry `:''}Events `}).join('')}
${runs.length?'':empty('No discovery runs yet')}
`)}
function logTabCount(tab,counts){return tab==='errors'?Number(counts?.errors||0):Number(counts?.[tab]||0)}
@@ -338,3 +338,46 @@ document.addEventListener('keydown',e=>{if(e.target.id==='messageRevisionInput'&
/* The first call above happens before this enhancement is defined. Run the
* non-destructive wrapper once so the panel exists before the first click. */
ensureMessageDialog(false);
+
+/* Prospect workspace v2: pipeline-first tracking with a compact alternative list. */
+let prospectViewMode=localStorage.getItem('prospect-view-mode')||'board';
+const PROSPECT_STAGES=[['pending','Needs review','Inspect evidence and decide'],['approved','Ready to contact','Approved for follow-up'],['contacted','Contacted','Already actioned'],['rejected','Not now','Kept out of the active queue']];
+function prospectDisplayRows(){
+ const q=String($('prospectSearch')?.value||'').trim().toLowerCase();
+ return prospects.filter(p=>{
+ const hay=[p.name,p.domain,p.search_category,p.category,p.address,p.emails,p.description].join(' ').toLowerCase();
+ const score=effectiveScore(p), site=String(p.website_status||'unknown').toLowerCase(), review=String(p.review_status||'pending');
+ const scoreOk=prospectScoreBand==='all'||(prospectScoreBand==='hot'&&score>=85)||(prospectScoreBand==='high'&&score>=70&&score<85)||(prospectScoreBand==='medium'&&score>=45&&score<70)||(prospectScoreBand==='low'&&score<45);
+ const siteOk=prospectWebsiteFilter==='all'||(prospectWebsiteFilter==='missing'&&(!p.website||site==='no_detected_website'))||(prospectWebsiteFilter==='broken'&&['broken','unreachable','error','timeout'].some(v=>site.includes(v)))||(prospectWebsiteFilter==='healthy'&&site==='healthy');
+ const emailOk=prospectEmailFilter==='all'||(prospectEmailFilter==='has_email'&&Boolean(String(p.emails||'').trim()))||(prospectEmailFilter==='no_email'&&!String(p.emails||'').trim());
+ const reviewOk=prospectReviewFilter==='all'||review===prospectReviewFilter;
+ return scoreOk&&siteOk&&emailOk&&reviewOk&&(!q||hay.includes(q));
+ }).sort((a,b)=>effectiveScore(b)-effectiveScore(a)||String(a.name||'').localeCompare(String(b.name||'')));
+}
+function prospectCard(p){
+ const score=effectiveScore(p),stage=String(p.review_status||'pending'),site=String(p.website_status||'unknown').replaceAll('_',' ');
+ return `${score} ${esc(p.source||'web')}
${esc(p.name||'Unnamed prospect')} ${esc(prospectCategory(p))}${p.address?` · ${esc(p.address)}`:''}
${site==='healthy'?'✓':'○'} ${esc(site)} ${p.emails?'✉ Public email':'✉ No email'}
Review details ${PROSPECT_STAGES.map(([key,label])=>`${label} `).join('')}
`;
+}
+function prospectBoard(){
+ const rows=prospectDisplayRows();
+ return `${PROSPECT_STAGES.map(([key,label,desc])=>{const items=rows.filter(p=>String(p.review_status||'pending')===key);return `
${items.map(prospectCard).join('')||`
No prospects here
`}
`}).join('')}
`;
+}
+function prospectCompactList(){
+ const rows=prospectDisplayRows();
+ return `${rows.map(p=>{const score=effectiveScore(p),stage=String(p.review_status||'pending');return `${score} ${esc(p.name)} ${esc(prospectCategory(p))}${p.address?` · ${esc(p.address)}`:''} ${esc(String(p.website_status||'unknown').replaceAll('_',' '))} ${p.emails?'Has email':'No email'} ${esc(PROSPECT_STAGES.find(s=>s[0]===stage)?.[1]||'Needs review')} › `}).join('')||empty('No prospects match these filters')}
`;
+}
+function prospectWorkspace(){
+ const rows=prospectDisplayRows();
+ const body=prospectViewMode==='list'?prospectCompactList():prospectBoard();
+ const pending=prospects.filter(p=>String(p.review_status||'pending')==='pending').length;
+ const hot=prospects.filter(p=>effectiveScore(p)>=85).length;
+ $('prospectTable').innerHTML=body;
+ const count=$('prospectResultCount');if(count)count.textContent=`Showing ${rows.length} of ${prospects.length}`;
+ const summary=$('prospectSummary');if(summary)summary.innerHTML=`${pending} need review${hot} high potential${prospects.filter(p=>p.emails).length} with public email `;
+}
+prospectTable=function(){return prospectViewMode==='list'?prospectCompactList():prospectBoard()};
+renderProspects=function(){
+ $('view').innerHTML=layout('Prospects','A calm workspace for deciding what matters next. Move each prospect through the pipeline as you work.',`Start in Needs review . Open a card for evidence, then move it to the next stage. The list view is best for quick scanning.
`);if($('prospectScoreBand'))$('prospectScoreBand').value=prospectScoreBand;if($('prospectWebsiteFilter'))$('prospectWebsiteFilter').value=prospectWebsiteFilter;if($('prospectEmailFilter'))$('prospectEmailFilter').value=prospectEmailFilter;if($('prospectReviewFilter'))$('prospectReviewFilter').value=prospectReviewFilter;prospectWorkspace();
+};
+document.addEventListener('click',e=>{const toggle=e.target.closest('[data-prospect-view]');if(toggle){e.preventDefault();prospectViewMode=toggle.dataset.prospectView;localStorage.setItem('prospect-view-mode',prospectViewMode);renderProspects();return}const open=e.target.closest('[data-board-open]');if(open&&!e.target.closest('select')){e.preventDefault();e.stopPropagation();openDrawer(open.dataset.boardOpen);}},true);
+document.addEventListener('change',async e=>{const move=e.target.closest('[data-board-status]');if(!move)return;e.preventDefault();e.stopPropagation();const id=move.dataset.boardStatus,status=move.value;move.disabled=true;try{const updated=await api(`/api/prospects/${id}/review`,{method:'PATCH',body:JSON.stringify({status})});const index=prospects.findIndex(p=>String(p.id)===String(id));if(index>=0)prospects[index]={...prospects[index],...updated};prospectWorkspace();}catch(error){move.disabled=false;alert(`Could not move prospect: ${error.message}`)}},true);
diff --git a/web/index.html b/web/index.html
index 1881eb3..9897803 100644
--- a/web/index.html
+++ b/web/index.html
@@ -1,3 +1,3 @@
-ProspectOS
-
ProspectOS · public data only · manual review before contact
+ProspectOS
+
ProspectOS · public data only · manual review before contact CLIENT-SPECIFIC DRAFT Write a message for this prospect Use verified facts and your offer to create an editable draft.
× Your offer or instruction
Generate client-specific draft Subject Email draft Copy draft Close
diff --git a/web/style.css b/web/style.css
index 8b46380..6ccf3c5 100644
--- a/web/style.css
+++ b/web/style.css
@@ -183,3 +183,9 @@ input::placeholder { color: #aeb6c5; } input:focus, select:focus { border-color:
.message-revision-composer{display:grid;gap:7px;padding:12px 14px 14px;border-top:1px solid #edf0f5;background:#fff}.message-revision-composer label{color:#627087;font-size:10px;font-weight:850}.message-revision-composer textarea{min-height:74px;margin:0;resize:vertical;font-size:12px;line-height:1.5}.message-revision-composer .notice{margin:0;font-size:10px;line-height:1.4}.message-revision-composer button{width:100%;min-height:40px}
.message-dialog{max-height:calc(100vh - 24px);overflow:auto}.message-dialog form{min-width:0}.message-dialog .draft-output{min-width:0}
@media(max-width:560px){.message-revision-head{display:block}.message-chat-badge{display:inline-block;margin-top:9px}.message-chat-messages{max-height:130px}}
+
+/* Prospect workspace overhaul: quiet surfaces, stronger hierarchy, pipeline-first tracking. */
+:root{--ink:#1d2433;--muted:#707a8d;--line:#e4e8ef;--soft:#f7f8fb;--purple:#5b55d6;--purple-dark:#4741b5;--navy:#20283b;--green:#168567;--amber:#a76912;--red:#b74d5c}
+body{background:#f5f6f8;color:var(--ink)}.app-shell{min-height:100vh}.sidebar{background:#20283b;border-right:0;box-shadow:12px 0 35px #15203b12}.brand{color:#f7f9fd}.brand small{color:#aeb8ca}.brand-mark{background:#8b85ff;box-shadow:0 0 0 5px #ffffff14}.sidebar nav a{color:#adb7c9;border-radius:9px;margin:3px 12px;padding:11px 13px;font-weight:700}.sidebar nav a:hover,.sidebar nav a.active{background:#ffffff12;color:#fff}.sidebar nav a.active:before{background:#a8a3ff}.main{background:#f5f6f8}.topbar{background:#fff;border-bottom:1px solid #e8ebf1;padding:18px 32px}.crumb{color:#929bad}.top-actions{color:#8b95a7}.page-head{padding:34px 32px 24px}.page-head h1{font-size:31px;letter-spacing:-.045em}.page-head p{color:#737d90;max-width:650px}.eyebrow{color:#625bd8;letter-spacing:.15em}.panel{border:1px solid #e4e8ef;border-radius:16px;background:#fff;box-shadow:0 10px 30px #25355408}.primary{background:#5b55d6;border-radius:9px;box-shadow:0 8px 18px #5b55d626}.secondary{border-radius:9px;border-color:#dfe4ed;background:#fff;color:#4f5b70}.metric{border-radius:14px;border-color:#e4e8ef;background:#fff}.metric b{font-size:30px}.search input,.prospect-toolbar select,.prospect-toolbar button{min-height:40px}
+.prospect-workspace-head{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:0 32px 20px}.prospect-summary{display:flex;flex-wrap:wrap;gap:9px}.prospect-summary span{padding:9px 12px;border:1px solid #e2e6ee;border-radius:999px;background:#fff;color:#758095;font-size:11px}.prospect-summary b{color:#20283b;margin-right:4px}.prospect-view-toggle{display:flex;padding:3px;border:1px solid #dfe4ed;border-radius:10px;background:#eef0f5}.prospect-view-toggle button{border:0;background:transparent;color:#788397;border-radius:7px;padding:8px 14px;font-size:11px;font-weight:850;cursor:pointer}.prospect-view-toggle button.active{background:#fff;color:#4039ad;box-shadow:0 2px 7px #24345416}.prospect-workspace-panel{margin:0 32px;padding:18px}.prospect-toolbar{display:flex;align-items:center;gap:9px;flex-wrap:wrap}.prospect-search{display:flex;align-items:center;gap:8px;flex:1 1 260px;min-height:40px;border:1px solid #dfe4ed;border-radius:9px;background:#fbfcfe;padding:0 11px;color:#8b95a7}.prospect-search input{width:100%;border:0;outline:0;background:transparent;font:inherit;font-size:12px;color:var(--ink)}.prospect-toolbar select{border:1px solid #dfe4ed;border-radius:9px;background:#fff;padding:0 9px;color:#5f6b80;font:inherit;font-size:11px}.result-count{margin-left:auto;color:#8a94a5;font-size:11px;white-space:nowrap}.workspace-help{margin:14px 0 20px;padding:11px 13px;border-radius:9px;background:#f6f7fb;color:#707b8e;font-size:11px}.workspace-help b{color:#4b44bd}.pipeline-board{display:grid;grid-template-columns:repeat(4,minmax(220px,1fr));gap:13px;align-items:start}.pipeline-column{min-width:0;border:1px solid #e6e9ef;border-radius:13px;background:#f8f9fb;overflow:hidden}.pipeline-column>header{display:flex;justify-content:space-between;gap:10px;padding:15px 14px 12px;border-bottom:1px solid #e7eaf0}.pipeline-column>header h2{margin:0;font-size:13px;letter-spacing:-.02em}.pipeline-column>header p{margin:4px 0 0;color:#8a94a6;font-size:10px}.pipeline-column>header>span{display:grid;place-items:center;min-width:25px;height:25px;border-radius:8px;background:#e9ebf3;color:#59647a;font-size:11px;font-weight:850}.pipeline-pending>header{border-top:3px solid #7a72e4}.pipeline-approved>header{border-top:3px solid #28a77e}.pipeline-contacted>header{border-top:3px solid #5091c9}.pipeline-rejected>header{border-top:3px solid #b6bdc9}.pipeline-cards{display:grid;gap:9px;padding:10px}.pipeline-card{border:1px solid #e2e6ed;border-radius:11px;background:#fff;box-shadow:0 4px 12px #26365308;overflow:hidden;transition:transform .15s ease,box-shadow .15s ease}.pipeline-card:hover{transform:translateY(-2px);box-shadow:0 10px 20px #26365312}.pipeline-card-open{display:block;width:100%;border:0;background:transparent;text-align:left;padding:13px 13px 10px;color:inherit;cursor:pointer}.pipeline-card-top{display:flex;align-items:center;justify-content:space-between}.pipeline-source{color:#929bad;font-size:9px;text-transform:uppercase;letter-spacing:.08em}.pipeline-card h3{margin:11px 0 4px;font-size:13px;letter-spacing:-.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pipeline-meta{margin:0;color:#7c8799;font-size:10px;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pipeline-facts{display:flex;flex-wrap:wrap;gap:7px;margin-top:12px;color:#8a94a5;font-size:9px}.pipeline-facts .fact-good{color:#168567}.pipeline-card-actions{display:flex;align-items:center;justify-content:space-between;gap:7px;padding:8px 10px;border-top:1px solid #eef0f4;background:#fbfcfd}.pipeline-card-actions select{max-width:122px;border:1px solid #dfe4ec;border-radius:7px;background:#fff;padding:5px;color:#68748a;font:inherit;font-size:9px}.text-action{border:0;background:transparent;color:#5b55d6;padding:5px 2px;font:inherit;font-size:10px;font-weight:800;cursor:pointer}.score{display:inline-grid;place-items:center;min-width:30px;height:25px;padding:0 6px;border-radius:7px;font-size:11px;font-weight:900}.score.hot{background:#fff0e9;color:#c15c2f}.score.warm{background:#fff7dc;color:#9b6814}.score.cool{background:#edf1ff;color:#5658b9}.pipeline-empty{padding:25px 12px;text-align:center;color:#a0a8b7;font-size:10px}.compact-list{display:grid;border:1px solid #e4e8ef;border-radius:11px;overflow:hidden}.compact-row{display:grid;grid-template-columns:38px minmax(180px,1.5fr) 1fr 1fr 1fr 20px;align-items:center;gap:12px;width:100%;border:0;border-bottom:1px solid #edf0f4;background:#fff;text-align:left;padding:13px 14px;color:inherit;cursor:pointer}.compact-row:last-child{border-bottom:0}.compact-row:hover{background:#fafaff}.compact-main{display:grid;gap:4px;min-width:0}.compact-main b{font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-main small,.compact-site,.compact-email,.compact-stage{color:#7f899a;font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-stage{color:#5c57bd;font-weight:750}.empty{padding:35px 15px}.detail-drawer{box-shadow:-15px 0 45px #25355418}
+@media(max-width:1050px){.pipeline-board{grid-template-columns:repeat(2,minmax(220px,1fr))}.prospect-toolbar .result-count{margin-left:0}}@media(max-width:680px){.topbar{padding:15px 18px}.page-head{padding:26px 18px 18px}.prospect-workspace-head{padding:0 18px 16px;display:block}.prospect-view-toggle{margin-top:13px;width:max-content}.prospect-workspace-panel{margin:0 18px;padding:12px}.pipeline-board{grid-template-columns:1fr}.prospect-toolbar{display:grid;grid-template-columns:1fr 1fr}.prospect-search{grid-column:1/-1}.prospect-toolbar select,.prospect-toolbar button{width:100%}.result-count{grid-column:1/-1}.compact-row{grid-template-columns:34px minmax(0,1fr) 18px;gap:9px}.compact-site,.compact-email,.compact-stage{display:none}}
\ No newline at end of file