Scanning marketplaces now

Never miss
a deal again.

MotoHawk is an AI agent that hunts motorcycle deals across every marketplace 24/7. It watches so you don't have to.

Start Hunting Free → See how it works

How it works

Set it. Forget it. Ride it.

01

Tell MotoHawk what you want

Year range, make, model, max price, max miles, location radius. Your perfect bike, defined once.

02

It hunts while you sleep

Cycle Trader, Facebook Marketplace, Craigslist, eBay Motors, dealer sites, forums. All monitored. Continuously.

03

Get alerts that matter

Only deals scored below market value. Price history, days listed, seller patterns. Not noise, just signal.

Loading hunts…
Deal Alerts
Listings scoring 15%+ below market average.
Loading alerts…

The best deals don't wait. Neither should you.

MotoHawk runs autonomously, scanning every corner of the motorcycle market so the perfect bike finds you, not the other way around.

Start Hunting Free →
(async function init() { try { var res = await fetch('/api/auth/me'); if (res.ok) { var d = await res.json(); setUser(d.user); showApp(); } } catch(e) {} })(); function openAuth(view) { document.getElementById('authModal').classList.add('open'); switchAuth(view); } function closeAuth() { document.getElementById('authModal').classList.remove('open'); } function switchAuth(view) { document.getElementById('signupView').style.display = view === 'signup' ? '' : 'none'; document.getElementById('loginView').style.display = view === 'login' ? '' : 'none'; } document.getElementById('authModal').addEventListener('click', function(e) { if (e.target === e.currentTarget) closeAuth(); }); async function handleSignup() { var name = document.getElementById('signupName').value.trim(); var email = document.getElementById('signupEmail').value.trim(); var password = document.getElementById('signupPassword').value; var errEl = document.getElementById('signupError'); errEl.classList.remove('show'); if (!email || !password) { showErr(errEl, 'Email and password required'); return; } try { var res = await fetch('/api/auth/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password }) }); var data = await res.json(); if (!res.ok) { showErr(errEl, data.error || 'Signup failed'); return; } setUser(data.user); closeAuth(); showApp(); toast('Account created!', 'success'); } catch(e) { showErr(errEl, 'Network error'); } } async function handleLogin() { var email = document.getElementById('loginEmail').value.trim(); var password = document.getElementById('loginPassword').value; var errEl = document.getElementById('loginError'); errEl.classList.remove('show'); if (!email || !password) { showErr(errEl, 'Email and password required'); return; } try { var res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); var data = await res.json(); if (!res.ok) { showErr(errEl, data.error || 'Login failed'); return; } setUser(data.user); closeAuth(); showApp(); toast('Welcome back!', 'success'); } catch(e) { showErr(errEl, 'Network error'); } } async function handleLogout() { await fetch('/api/auth/logout', { method: 'POST' }); currentUser = null; document.getElementById('app').classList.remove('active'); document.getElementById('landing').style.display = ''; toast('Signed out'); } function showErr(el, msg) { el.textContent = msg; el.classList.add('show'); } function setUser(user) { currentUser = user; document.getElementById('userChip').textContent = user.email; } function showApp() { document.getElementById('landing').style.display = 'none'; document.getElementById('app').classList.add('active'); loadHunts(); loadAlerts(); } function switchTab(tab) { document.querySelectorAll('.nav-tab').forEach(function(t) { t.classList.toggle('active', t.dataset.tab === tab); }); document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.toggle('active', p.id === tab + 'Panel'); }); } async function loadHunts() { try { var res = await fetch('/api/hunts'); var data = await res.json(); hunts = data.hunts || []; renderHunts(); } catch(e) { document.getElementById('huntsContainer').innerHTML = '
Failed to load hunts.
'; } } function renderHunts() { var el = document.getElementById('huntsContainer'); if (!hunts.length) { el.innerHTML = '
🔭
No hunts yet
Create your first hunt to start monitoring for deals.
'; return; } el.innerHTML = '
' + hunts.map(huntCardHtml).join('') + '
'; } function huntCardHtml(h) { var criteria = []; if (h.make) criteria.push(h.make); if (h.model) criteria.push(h.model); if (h.year_min || h.year_max) criteria.push((h.year_min||'') + '\u2013' + (h.year_max||'')); if (h.max_price) criteria.push('\u2264$' + Number(h.max_price).toLocaleString()); if (h.location) criteria.push(h.location + ' +' + (h.radius_miles||100) + 'mi'); var lastScraped = h.last_scraped_at ? 'Last checked ' + timeAgo(h.last_scraped_at) : 'Not yet checked'; return '
' + esc(h.name) + '
' + (h.active?'ACTIVE':'PAUSED') + '
' + (criteria.length ? criteria.map(function(c){return ''+esc(c)+'';}).join('') : 'All motorcycles') + '
' + lastScraped + '' + (h.alert_count||0) + ' alerts
'; } function toggleNewHuntForm() { var form = document.getElementById('newHuntForm'); var btn = document.getElementById('newHuntBtn'); var open = form.style.display === 'none'; form.style.display = open ? '' : 'none'; btn.textContent = open ? '\u2715 Cancel' : '+ New Hunt'; } async function handleCreateHunt() { var name = document.getElementById('huntName').value.trim(); if (!name) { toast('Hunt name is required', 'error'); return; } var body = { name: name, make: document.getElementById('huntMake').value.trim() || null, model: document.getElementById('huntModel').value.trim() || null, yearMin: parseInt(document.getElementById('huntYearMin').value) || null, yearMax: parseInt(document.getElementById('huntYearMax').value) || null, maxPrice: parseInt(document.getElementById('huntMaxPrice').value) || null, location: document.getElementById('huntLocation').value.trim() || null, radiusMiles: parseInt(document.getElementById('huntRadius').value) || 100 }; try { var res = await fetch('/api/hunts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); var data = await res.json(); if (!res.ok) { toast(data.error || 'Failed to create hunt', 'error'); return; } hunts.unshift(data.hunt); renderHunts(); toggleNewHuntForm(); clearHuntForm(); toast("Hunt created! We'll start checking shortly.", 'success'); } catch(e) { toast('Network error', 'error'); } } function clearHuntForm() { ['huntName','huntMake','huntModel','huntYearMin','huntYearMax','huntMaxPrice','huntLocation','huntRadius'] .forEach(function(id) { var el = document.getElementById(id); if(el) el.value=''; }); } async function toggleHuntActive(id, active) { try { var res = await fetch('/api/hunts/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ active: active }) }); if (!res.ok) throw new Error('update failed'); var hunt = hunts.find(function(h){return h.id===id;}); if (hunt) hunt.active = active; renderHunts(); toast(active ? 'Hunt resumed' : 'Hunt paused'); } catch(e) { toast('Failed to update hunt', 'error'); } } async function deleteHunt(id) { if (!confirm('Delete this hunt? All associated alerts will also be deleted.')) return; try { var res = await fetch('/api/hunts/' + id, { method: 'DELETE' }); if (!res.ok) throw new Error('delete failed'); hunts = hunts.filter(function(h){return h.id!==id;}); renderHunts(); toast('Hunt deleted'); } catch(e) { toast('Failed to delete hunt', 'error'); } } async function loadAlerts() { try { var res = await fetch('/api/hunts/alerts'); var data = await res.json(); alerts = data.alerts || []; renderAlerts(); var badge = document.getElementById('alertBadge'); if (alerts.length > 0) badge.textContent = '(' + alerts.length + ')'; } catch(e) { document.getElementById('alertsContainer').innerHTML = '
Failed to load alerts.
'; } } function renderAlerts() { var el = document.getElementById('alertsContainer'); if (!alerts.length) { el.innerHTML = '
No deals yet
We\'re watching. The moment something drops 15%+ below market, it\'ll appear here and we\'ll email you.
'; return; } el.innerHTML = '
' + alerts.map(alertCardHtml).join('') + '
'; } function alertCardHtml(a) { var pct = a.pct_below_market ? Math.abs(parseFloat(a.pct_below_market)) : 0; var scoreClass = pct >= 25 ? 'exceptional' : pct >= 15 ? 'good' : 'fair'; var scoreColor = pct >= 25 ? 'var(--green)' : pct >= 15 ? 'var(--yellow)' : 'var(--accent)'; var title = a.title || ((a.year||'') + ' ' + (a.make||'') + ' ' + (a.model||'')).trim(); var source = a.source === 'cycletrader' ? 'Cycle Trader' : 'Craigslist'; var tags = []; if (a.year) tags.push(a.year); if (a.make) tags.push(a.make); if (a.model) tags.push(a.model); if (a.mileage) tags.push(Number(a.mileage).toLocaleString() + ' mi'); if (a.listing_location) tags.push(a.listing_location); tags.push(source); return '
-' + pct.toFixed(0) + '%below
' + esc(title) + '
' + tags.map(function(t){return ''+esc(String(t))+'';}).join('') + '
Hunt: ' + esc(a.hunt_name) + ' · ' + timeAgo(a.created_at) + '
$' + Number(a.price).toLocaleString() + '' + (a.market_avg_price ? '
$' + Number(a.market_avg_price).toLocaleString() + '
' : '') + 'View →
'; } function esc(s) { if (!s && s !== 0) return ''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function timeAgo(iso) { var ms = Date.now() - new Date(iso).getTime(); var mins = Math.floor(ms / 60000); if (mins < 2) return 'just now'; if (mins < 60) return mins + 'm ago'; var hrs = Math.floor(mins / 60); if (hrs < 24) return hrs + 'h ago'; return Math.floor(hrs / 24) + 'd ago'; } var toastTimeout; function toast(msg, type) { var el = document.getElementById('toast'); el.textContent = msg; el.className = 'toast show' + (type ? ' ' + type : ''); clearTimeout(toastTimeout); toastTimeout = setTimeout(function(){ el.classList.remove('show'); }, 3000); } document.getElementById('signupPassword').addEventListener('keydown', function(e){ if(e.key==='Enter') handleSignup(); }); document.getElementById('loginPassword').addEventListener('keydown', function(e){ if(e.key==='Enter') handleLogin(); });