V
VaultHire
Intelligence-led recruitment

Find the roles
before your
competition does.

Millions of live roles and signals every month. 100% direct employer roles. Zero agency noise. Built by recruitment owners — for you.

25 Founders seats500 Pro seats per country
Built by recruitment owners, for the recruitment world.
This is built for YOU.
There are 30,000+ UK recruitment businesses. Only the elite few will ever access VaultHire.
Live signal feed
Every result is a direct employer. Zero recruitment agency adverts. Ever.
25
Founders seats ever
Millions
live roles monthly
Zero
agency saturation
Detected
${v.det||'Recent'}
Salary Range
${v.mn>0&&v.mx>0?fs(v.mn,v.mx):'Competitive'}
Est. Placement Fee
${v.fee>0?'£'+(v.fee/1000).toFixed(1)+'k':'N/A'}
Headcount
${v.hc||'N/A'}
Why ${v.co} is likely to hire
${(v.sigs||[]).length} market signals · ${(v.sigs||[]).filter(function(s){return s.strong;}).length} strong indicators
${v.lh||70}%
hire likelihood
${(v.insights||[]).map(function(i){return '
'+i+'
';}).join('')} ${(v.sigs||[]).map(function(s,i){return '
'+s.src+'· '+s.type+''+s.score+'%'+s.age+' ago'+(s.strong?'Strong signal':'Supporting')+'
'+s.desc+'
'+(s.link?'View source ↗':'')+'
';}).join('')}
Tech Stack ${(v.tech||[]).length} technologies
${(v.tech||[]).map(function(t){return ''+t+'';}).join('')}
Hiring Manager
${ul?'Contact unlocked':'Contact data gated'}
${v.hm?v.hm.init:'HM'}
${v.hm?v.hm.name:'Hiring Manager'}
${v.hm?v.hm.role:''}
${v.hm?v.hm.resp:70}% response rate
${emailIco}${ul&&v.hm?(v.hm.email||v.hm.name.split(' ')[0].toLowerCase()+'.'+((v.hm.name.split(' ')[1]||'x').toLowerCase())+'@'+v.co.toLowerCase().replace(/[^a-z]/g,'')+'.com'):'████████████████████'}
${phoneIco}${ul&&v.hm?(v.hm.phone||'+44 7'+Math.floor(700+(v.hm.resp||70))+' '+Math.floor(100000+(v.hm.resp||70)*1000)):'███ ███ ████'}
${linkIco}${ul&&v.hm?(v.hm.linkedin||'linkedin.com/in/'+v.hm.name.toLowerCase().replace(/\s/g,'')):'█████████████'}
${!ul?'
Hiring manager contact data is gated
Unlock verified email, direct phone, and LinkedIn profile for 1 credit. You have '+S.credits+' credits remaining.
':''+((['pro','pro_plus'].includes(S.user&&S.user.plan)&&ul)?'':'')}
Credit Usage${S.user.plan} plan
60
Total
${60-S.credits}
Used
${S.credits}
Left
Renews 1 May 2026
Similar Vacancies ${(S.vacs.length?S.vacs:SEED).filter(function(x){return x.id!==v.id&&(x.sector===v.sector||x.mode===v.mode);}).length} matches
${(S.vacs.length?S.vacs:SEED).filter(function(x){return x.id!==v.id&&(x.sector===v.sector||x.mode===v.mode);}).slice(0,4).map(function(sv){var conf=sv.score>=85?'Verified':sv.score>=70?'Probable':'Indicative';var cc=conf==='Verified'?'l-vrf':conf==='Probable'?'l-prb':'l-ind';return '
'+sv.title+' T'+sv.tier+' '+conf+'
'+sv.co+'
'+sv.loc+''+sv.mode+''+fs(sv.mn,sv.mx)+'
'+(function(){var p=sv.posted||'';if(!p||p==='null'||p.includes('NaN'))return 'Recently';return p.includes('ago')||p==='Just now'||p==='Yesterday'?p:p+' ago';})()+'
';}).join('')}
`; window.toggleSig=function(key){S.sigOpen[key]=!S.sigOpen[key];var body=document.getElementById('sigbody-'+key);var chev=document.getElementById('chev-'+key);if(body)body.style.display=S.sigOpen[key]?'block':'none';if(chev)chev.classList.toggle('open',S.sigOpen[key]);}; } // ═══ SAVED ═══ function rSaved(){ var savedObj=getSavedVacsCache();var sv=Object.values(savedObj); S.vacs.forEach(function(v){if(S.saved.has(v.id)&&!savedObj[v.id])sv.push(v);}); tb('Saved Jobs/'+sv.length+' jobs saved to your vault'); document.getElementById('pg').innerHTML=sv.length?sv.map(function(v){return vRow(v);}).join(''):'
No saved jobs yet
Click Save Job on any vacancy to bookmark it here.
'; } // ═══ DASHBOARD ═══ function rDash(){ tb('Dashboard/Welcome back, '+S.user.name.split(' ')[0]+''); var hotVacs=(S.vacs.length?S.vacs:SEED).filter(function(v){return v.score>=80;}); document.getElementById('pg').innerHTML='
' +'
Live vacancies
'+(S.vacs.length||SEED.length)+'
+3 new since yesterday
' +'
Hot signals
'+hotVacs.length+'
+1 new this hour
' +'
Saved
'+S.saved.size+'
Updated today
' +'
Credits remaining
'+S.credits+'
of 60 this month
' +'
' +'
' +'
Recent activity
' +[['Hot signal detected','Head of Engineering at Orbit Health — £22M Series B closed','2m','var(--rd)','v4'],['New vacancy matched','Senior Backend Engineer at Flint Analytics — T1 / FinTech','15m','var(--gn)','v1'],['Credit used','HM contact unlocked — Flint Analytics','1h','var(--am)','v1'],['Warm signal','Principal Engineer at Lumi Capital — Ashby live, no agency','3h','var(--am)','v3'],['Alert matched','Engineering Manager matched your Warm+ threshold','5h','var(--gn)',null]].map(function(a){return '
"+a[0]+" — "+a[1]+"
"+a[2]+" ago
";}).join('')+'
' +'
Top opportunities
'+hotVacs.slice(0,5).map(function(v){return '
'+v.title+' — '+v.co+'
'+fs(v.mn,v.mx)+' · '+v.loc+'
'+(function(){var p=v.posted||'';if(!p||p==='null'||String(p).includes('NaN'))return 'Recently';return p.includes('ago')||p==='Just now'?p:p+' ago';})()+'
';}).join('')+'
' +'
'; } // ═══ ALERTS ═══ function rAlerts(){ var searches=getSavedSearches(); tb('Job Alerts/Saved searches — scanned on every login',''); if(!searches.length){document.getElementById('pg').innerHTML='
No Job Alerts yet
Run a search on the Vacancies page, then click + Create Alert next to the search box.
';return;} document.getElementById('pg').innerHTML=searches.map(function(s,i){ var nb=s.newCount>0?''+s.newCount+' new':''; var lr=s.lastRun?'Last scanned '+s.lastRun:'Not yet scanned'; return '
' +'
' +'
'+s.query+''+nb+'
' +'
'+lr+' · Created '+s.created+'
' +'' +'
'; }).join(''); } function loadSavedSearch(idx){var searches=getSavedSearches();var s=searches[idx];if(!s)return;searches[idx].newCount=0;saveSavedSearches(searches);S.page='vacancies';S.loading=true;render();showLoader();fetchLiveVacs(s.query);} function toggleAlertActive(idx){var searches=getSavedSearches();if(!searches[idx])return;searches[idx].active=searches[idx].active===false?true:false;saveSavedSearches(searches);syncSearchToD1(searches[idx]);rAlerts();} async function deleteSavedSearch(idx){ var searches=getSavedSearches();var s=searches[idx];if(!s)return;var rid=s.id; searches.splice(idx,1);saveSavedSearches(searches);rAlerts(); var deleted=JSON.parse(localStorage.getItem('vh_deleted_searches')||'[]'); if(rid&&!deleted.includes(rid)){deleted.push(rid);localStorage.setItem('vh_deleted_searches',JSON.stringify(deleted));} if(rid){var ok=await removeSearchFromD1(rid);if(ok){deleted=deleted.filter(function(d){return d!==rid;});localStorage.setItem('vh_deleted_searches',JSON.stringify(deleted));toast('Alert removed','var(--rd)');}else{toast('Alert removed locally — will sync when reconnected','var(--am)');}}else{toast('Alert removed','var(--rd)');} } async function runSingleSearch(idx){ var searches=getSavedSearches();var s=searches[idx];if(!s)return;var token=S.token||localStorage.getItem('vh_token');if(!token)return; toast('Scanning "'+s.query+'"…'); try{var resp=await fetch(WORKER+'/search/jobs',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+token},body:JSON.stringify({query:s.query,filter:'all',page:1})});if(!resp.ok)return;var data=await resp.json();var results=data.results||[];var seenIds=s.seenIds||[];var newR=results.filter(function(v){return !seenIds.includes(v.id);});searches[idx].lastRun=new Date().toLocaleDateString('en-GB');searches[idx].lastRunTs=Date.now();searches[idx].newCount=(searches[idx].newCount||0)+newR.length;searches[idx].seenIds=seenIds.concat(newR.map(function(v){return v.id;})).slice(-500);searches[idx].latestResults=results.slice(0,10);saveSavedSearches(searches);rAlerts();toast(newR.length>0?newR.length+' new vacancies found':'No new vacancies since last scan');} catch(e){toast('Scan failed: '+e.message,'var(--rd)');} } // ═══ SPEC MATCH ═══ function rSpecMatch(){ tb('Spec Match/CV-to-vacancy matching'); var pg=document.getElementById('pg');if(!pg)return; if(S.cvData){if(S.specResults&&S.specResults.length>0){renderSpecResults(S.specResults);return;}showSpecConfirm(S.cvData);return;} pg.innerHTML=''; var wrap=document.createElement('div');wrap.className='spec-drop';wrap.id='spec-drop-zone'; var icon=document.createElement('div');icon.className='spec-icon';icon.textContent='📄'; var title=document.createElement('div');title.className='spec-title';title.textContent='Upload a CV to find live matches'; var sub=document.createElement('div');sub.className='spec-sub';sub.textContent='PDF or Word document · Extracts skills, seniority & location automatically'; var btnWrap=document.createElement('div');btnWrap.style.marginTop='1rem'; var btn=document.createElement('button');btn.className='btn bgn';btn.textContent='Choose file'; var inp=document.createElement('input');inp.type='file';inp.accept='.pdf,.doc,.docx,.txt';inp.style.display='none'; btnWrap.appendChild(btn);btnWrap.appendChild(inp); wrap.appendChild(icon);wrap.appendChild(title);wrap.appendChild(sub);wrap.appendChild(btnWrap);pg.appendChild(wrap); var note=document.createElement('div');note.style.cssText='font-size:12px;color:var(--t3);text-align:center;margin-top:0.75rem';note.textContent='Supported: PDF, Word (.docx), plain text · Uses 1 Spec Match credit per search';pg.appendChild(note); btn.addEventListener('click',function(e){e.stopPropagation();inp.click();}); wrap.addEventListener('click',function(){inp.click();}); inp.addEventListener('change',function(){if(this.files[0])handleCVUpload(this.files[0]);}); wrap.addEventListener('dragover',function(e){e.preventDefault();this.classList.add('drag');}); wrap.addEventListener('dragleave',function(){this.classList.remove('drag');}); wrap.addEventListener('drop',function(e){e.preventDefault();this.classList.remove('drag');var f=e.dataTransfer.files[0];if(f)handleCVUpload(f);}); // Recent matches section loadSpecHistory(pg); } function loadSpecHistory(pg){ var token=localStorage.getItem('vh_token'); if(!token)return; fetch(WORKER+'/spec/history',{headers:{'Authorization':'Bearer '+token}}) .then(function(r){return r.json();}) .then(function(d){ if(!d.ok||!d.matches||d.matches.length===0)return; var sec=document.createElement('div'); sec.style.cssText='margin-top:1.5rem'; sec.innerHTML='
Recent matches
' +d.matches.map(function(m){ var pct=m.score||0; var col=pct>=80?'var(--gn)':pct>=60?'var(--am)':'var(--t2)'; var dt=m.run_at?new Date(m.run_at).toLocaleDateString('en-GB',{day:'numeric',month:'short'}):'Recently'; return '
' +'
'+m.role+'
' +'
'+dt+'
' +'
'+pct+'%
' +'
'; }).join(''); if(pg)pg.appendChild(sec); }).catch(function(){}); } async function handleCVUpload(file){ var pg=document.getElementById('pg');if(!pg)return; pg.innerHTML='
Reading CV...
'; try{ var fileType='text',fileData=''; if(file.name.toLowerCase().endsWith('.pdf')){fileType='pdf';fileData=await new Promise(function(res,rej){var r=new FileReader();r.onload=function(){res(r.result.split(',')[1]);};r.onerror=function(){rej(new Error('Read failed'));};r.readAsDataURL(file);});} else if(file.name.toLowerCase().endsWith('.docx')){if(typeof mammoth==='undefined'){await new Promise(function(res,rej){var sc=document.createElement('script');sc.src='https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js';sc.onload=res;sc.onerror=function(){rej(new Error('mammoth load failed'));};document.head.appendChild(sc);});}var ab=await file.arrayBuffer();var result=await mammoth.extractRawText({arrayBuffer:ab});fileType='text';fileData=result.value;} else{fileData=await file.text();} var resp=await fetch(WORKER+'/cv/parse',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+(S.token||localStorage.getItem('vh_token')||'')},body:JSON.stringify({fileType:fileType,fileData:fileData,fileName:file.name})}); var data=await resp.json(); if(!data.ok){toast(data.error||'CV parse failed. Please try again.','var(--rd)');S.cvData=null;go('specmatch');return;} S.cvData=data.cv;S.specResults=null;showSpecConfirm(data.cv); }catch(e){toast('Error reading CV: '+e.message,'var(--rd)');S.cvData=null;go('specmatch');} } function showSpecConfirm(cv){ var pg=document.getElementById('pg');if(!pg)return; var name=cv.name||'Candidate',title=cv.title||'',loc=cv.location||'',skills=(cv.skills||[]).slice(0,8),seniority=cv.seniority||''; var init=name.split(' ').map(function(w){return w[0]||'';}).join('').substring(0,2).toUpperCase(); pg.innerHTML=''; var card=document.createElement('div');card.className='cv-card'; var av=document.createElement('div');av.className='cv-av';av.textContent=init; var info=document.createElement('div');info.className='cv-info'; var cvName=document.createElement('div');cvName.className='cv-name';cvName.textContent=name; var cvMeta=document.createElement('div');cvMeta.className='cv-meta';cvMeta.textContent=seniority+(seniority&&title?' · ':'')+title; var cvTags=document.createElement('div');cvTags.className='cv-tags'; skills.forEach(function(sk){var tag=document.createElement('span');tag.className='cv-tag';tag.textContent=sk;cvTags.appendChild(tag);}); info.appendChild(cvName);info.appendChild(cvMeta);info.appendChild(cvTags);card.appendChild(av);card.appendChild(info);pg.appendChild(card); function addField(labelText,id,value,placeholder){var lbl=document.createElement('label');lbl.className='fl';lbl.style.marginTop='0.75rem';lbl.textContent=labelText;var inp=document.createElement('input');inp.className='fi';inp.id=id;inp.value=value||'';inp.placeholder=placeholder||'';pg.appendChild(lbl);pg.appendChild(inp);return inp;} addField('Job title to search','spec-title-inp',title,'e.g. Senior Software Engineer'); addField('Location','spec-loc-inp',loc,'e.g. London or NE6 1AA'); var lbl3=document.createElement('label');lbl3.className='fl';lbl3.style.marginTop='0.75rem';lbl3.textContent='Search radius'; var sel=document.createElement('select');sel.className='fi';sel.id='spec-radius-inp'; [['0','UK-wide'],['10','Within 10 miles'],['20','Within 20 miles'],['30','Within 30 miles'],['50','Within 50 miles']].forEach(function(opt){var o=document.createElement('option');o.value=opt[0];o.textContent=opt[1];if(opt[0]==='20')o.selected=true;sel.appendChild(o);}); pg.appendChild(lbl3);pg.appendChild(sel); var btnRow=document.createElement('div');btnRow.style.cssText='display:flex;gap:8px;margin-top:1.25rem'; var searchBtn=document.createElement('button');searchBtn.className='btn bgn';searchBtn.style.flex='1';searchBtn.textContent='Find live matches →';searchBtn.addEventListener('click',runSpecSearch); var resetBtn=document.createElement('button');resetBtn.className='btn';resetBtn.style.cssText='background:var(--sf);border:1px solid var(--ln2);color:var(--t2)';resetBtn.textContent='Upload different CV';resetBtn.addEventListener('click',function(){S.cvData=null;S.specResults=null;go('specmatch');}); btnRow.appendChild(searchBtn);btnRow.appendChild(resetBtn);pg.appendChild(btnRow); } async function runSpecSearch(){ var titleInp=document.getElementById('spec-title-inp'),locInp=document.getElementById('spec-loc-inp'),radiusInp=document.getElementById('spec-radius-inp'); var title=titleInp?titleInp.value.trim():'',loc=locInp?locInp.value.trim():'',radius=radiusInp?parseInt(radiusInp.value):20; if(!title){toast('Please enter a job title to search','var(--am)');return;} var pg=document.getElementById('pg');if(!pg)return; pg.innerHTML='
Finding live matches for '+title+'...
'; try{ var token=S.token||localStorage.getItem('vh_token')||''; var resp=await fetch(WORKER+'/search/jobs',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+token},body:JSON.stringify({query:title,filter:'all',page:1,location:loc,radius:radius,minSalary:S.minSalary||0})}); var data=await resp.json(); if(!data.ok){if(data.trialExpired){go('billing');return;}toast(data.error||'Search failed','var(--rd)');showSpecConfirm(S.cvData);return;} var results=data.results||[]; if(!results.length){pg.innerHTML='
No live matches found for '+title+''+(loc?' near '+loc:'')+'.

';return;} var cvSkills=(S.cvData&&S.cvData.skills?S.cvData.skills:[]).map(function(sk){return sk.toLowerCase();}); results.forEach(function(v){var vText=(v.title+' '+(v.insights||[]).join(' ')+' '+(v.tags||[]).join(' ')).toLowerCase();var matches=cvSkills.filter(function(sk){return vText.includes(sk);}).length;v.specScore=Math.round(Math.min(100,Math.round((matches/Math.max(cvSkills.length,1))*100))*0.6+(v.score||50)*0.4);v.matchedSkills=cvSkills.filter(function(sk){return vText.includes(sk);}).slice(0,5);}); results.sort(function(a,b){return (b.specScore||0)-(a.specScore||0);});S.specResults=results; fetch(WORKER+'/credits/deduct',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+token}}).then(function(r){return r.json();}).then(function(d){if(d.ok){S.specCredits=Math.max(0,(S.specCredits||0)-1);updCreds();}}).catch(function(){}); renderSpecResults(results); }catch(e){toast('Search error: '+e.message,'var(--rd)');showSpecConfirm(S.cvData);} } function renderSpecResults(results){ var pg=document.getElementById('pg');if(!pg)return;pg.innerHTML=''; var hdr=document.createElement('div');hdr.style.cssText='display:flex;align-items:center;justify-content:space-between;margin-bottom:0.85rem'; var hdrLeft=document.createElement('div');hdrLeft.innerHTML=''+results.length+' live matchesfor '+((S.cvData&&S.cvData.title)||'candidate')+''; var newSearchBtn=document.createElement('button');newSearchBtn.className='btn';newSearchBtn.style.cssText='font-size:12px;background:var(--sf);border:1px solid var(--ln2);color:var(--t3)';newSearchBtn.textContent='New search';newSearchBtn.addEventListener('click',function(){S.cvData=null;S.specResults=null;go('specmatch');}); hdr.appendChild(hdrLeft);hdr.appendChild(newSearchBtn);pg.appendChild(hdr); results.forEach(function(v){ var score=v.specScore||0;var scoreCol=score>=80?'var(--gn)':score>=60?'var(--am)':'var(--t3)'; var row=document.createElement('div');row.className='vr';row.style.cursor='pointer';row.addEventListener('click',function(){S.av=v.id;S.page='detail';render();}); var scoreDiv=document.createElement('div');scoreDiv.style.cssText='display:flex;flex-direction:column;align-items:center;min-width:52px;padding:0 12px 0 4px;border-right:1px solid var(--ln);margin-right:12px'; scoreDiv.innerHTML='
'+score+'
match
'; var main=document.createElement('div');main.style.cssText='flex:1;min-width:0'; main.innerHTML='
'+v.title+'
'+v.co+' · '+v.loc+' · '+(v.salary||'Competitive')+'
'; if(v.matchedSkills&&v.matchedSkills.length>0){var sr=document.createElement('div');sr.style.cssText='display:flex;gap:3px;flex-wrap:wrap';v.matchedSkills.forEach(function(sk){var tag=document.createElement('span');tag.style.cssText='font-size:10px;color:var(--gn);background:var(--gb);border:1px solid var(--gl);border-radius:3px;padding:1px 5px';tag.textContent=sk;sr.appendChild(tag);});main.appendChild(sr);} var right=document.createElement('div');right.style.cssText='display:flex;flex-direction:column;align-items:flex-end;gap:6px;padding-left:10px;flex-shrink:0'; var sigBg=v.signal==='HOT'?'var(--rb)':v.signal==='NEW'?'var(--gb)':'var(--ab)';var sigFg=v.signal==='HOT'?'var(--rd)':v.signal==='NEW'?'var(--gn)':'var(--am)'; right.innerHTML=''+(v.signal||'WARM')+''; var viewBtn=document.createElement('button');viewBtn.className='vr-view';viewBtn.textContent='View';viewBtn.addEventListener('click',function(e){e.stopPropagation();S.av=v.id;S.page='detail';render();});right.appendChild(viewBtn); row.appendChild(scoreDiv);row.appendChild(main);row.appendChild(right);pg.appendChild(row); }); } // ═══ CREDITS ═══ function rCredits(){ tb('Credits/ Unlock verified hiring manager contact data'); var plan=(S.user&&S.user.plan)||'founders'; var planLabel=plan==='pro_plus'?'PRO+':plan==='pro'?'PRO':plan==='founders'?'FOUNDERS':'TRIAL'; var planCredits=plan==='pro_plus'?75:plan==='pro'?50:plan==='founders'?25:5; var hmUsed=planCredits-S.credits; var hmPct=Math.round((hmUsed/planCredits)*100); var specTotal=S.specCredits||0; document.getElementById('pg').innerHTML= // ── Usage card ── '
' // HM credits card +'
' +'
HM Unlock Credits'+planLabel+' · '+planCredits+'/mo
' +'
' +'
'+planCredits+'
Total
' +'
'+hmUsed+'
Used
' +'
'+S.credits+'
Remaining
' +'
' +'
' +'
Unlocks verified email · phone · LinkedIn per hiring manager
' +'
' // Spec Match credits card +'
' +'
Spec Match Credits'+planCredits+'/mo
' +'
' +'
'+planCredits+'
Total
' +'
'+(planCredits-specTotal)+'
Used
' +'
'+specTotal+'
Remaining
' +'
' +'
' +'
Each CV match uses 1 credit · parses CV + scores against live vacancies
' +'
' +'
' // ── HM Top-up ── +'
Top up HM unlock credits
' +'
' +'
' +'
10
HM credits
' +'
£25
£2.50 per contact
' +'
' +'
' +'
Best value
' +'
25
HM credits
' +'
£55
£2.20 per contact
' +'
' +'
' +'
50
HM credits
' +'
£95
£1.90 per contact
' +'
' +'
' // ── Spec Match Top-up ── +'
Top up Spec Match credits
' +'
' +'
' +'
10
Spec credits
' +'
£15
£1.50 per match
' +'
' +'
' +'
Best value
' +'
25
Spec credits
' +'
£30
£1.20 per match
' +'
' +'
' +'
50
Spec credits
' +'
£55
£1.10 per match
' +'
' +'
'; } function buyHM(qty){ var el=event&&event.target; if(el){var card=el.closest('.cc');if(card){var msg=card.querySelector('.buy-soon');if(!msg){msg=document.createElement('div');msg.className='buy-soon';msg.style.cssText='font-size:11px;color:var(--am);margin-top:0.5rem;text-align:center;line-height:1.5';msg.innerHTML='Top-ups coming soon — contact us to add credits manually';card.appendChild(msg);}else{msg.style.display=msg.style.display==='none'?'block':'block';}return;}} toast('Top-ups coming soon — email hello@vaulthire.org to add credits manually','var(--am)'); } function buySpec(qty){ var el=event&&event.target; if(el){var card=el.closest('.cc');if(card){var msg=card.querySelector('.buy-soon');if(!msg){msg=document.createElement('div');msg.className='buy-soon';msg.style.cssText='font-size:11px;color:var(--am);margin-top:0.5rem;text-align:center;line-height:1.5';msg.innerHTML='Top-ups coming soon — contact us to add credits manually';card.appendChild(msg);}return;}} toast('Top-ups coming soon — email hello@vaulthire.org','var(--am)'); } function buyC(qty,type){if(type==='spec')buySpec(qty);else buyHM(qty);} // ═══ BILLING ═══ function rBilling(){ tb('Billing'); var WORKER='https://vaulthire-auth.stephen-00f.workers.dev'; fetch(WORKER+'/seats').then(function(r){return r.json();}).then(function(d){ _renderBilling(d); }).catch(function(){ _renderBilling({founders_remaining:25,founders_taken:0,pro_remaining:500,pro_taken:0,pro_plus_remaining:500,pro_plus_taken:0}); }); } function _renderBilling(seats){ var fRem=typeof seats.founders_remaining==='number'?seats.founders_remaining:25; var fTaken=seats.founders_taken||0; var pRem=seats.pro_remaining||500; var ppRem=seats.pro_plus_remaining||500; var fPct=Math.round((fTaken/25)*100); var fBarCol=fPct>=80?'var(--rd)':fPct>=60?'var(--am)':'var(--gn)'; var ann=S.bTab==='annual'; var fPrice=ann?49:59; var pPrice=ann?82:99; var ppPrice=ann?107:129; document.getElementById('pg').innerHTML= '
' +' ' +(fRem===0 ?'All 25 Founders seats claimed. '+pRem+' Pro seats remaining.' :''+fRem+' of 25 Founders seats remaining. Access closes permanently when all seats are filled.') +'
' +'
' +'
Reserve your VaultHire seat
' +'
500 seats per country. Non-renewable. Access closes when full.
' +'
' +'
' +'' +'' +'
' +'
' // FOUNDERS +'
' +'
' +'
Founders Seat
' +(fRem===0?'SOLD OUT' :fRem<=5?'ALMOST GONE':'') +'
' +'
Only 25 seats — worldwide, ever
' +'
' +'
'+fTaken+' claimed · '+fRem+' left
' +'
£'+fPrice+'/month
' +'
'+['Full vacancy feed access','Signal scoring and tier labels','ATS source detection','Save and export vacancies','Email alerts — daily digest','25 HM credits/month','10 phone credits/month','Email + LinkedIn + Phone'].map(function(f){return '
'+f+'
';}).join('')+'
' +'' +'
' // PRO +'
' +'
Most popular
' +'
Pro Seat
' +'
500 per country · '+pRem+' remaining
' +'
£'+pPrice+'/month
' +'
'+['Everything in Founders tier','Instant hot-signal alerts','50 HM credits/month','Verified email + LinkedIn only','20 AI-drafted outreach emails/month','Placement fee estimator','No phone — upgrade to Pro+'].map(function(f){return '
'+f+'
';}).join('')+'
' +'' +'
' // PRO+ +'
' +'
Pro+ Seat
' +'
500 per country · '+ppRem+' remaining
' +'
£'+ppPrice+'/month
' +'
'+['Everything in Pro tier','75 HM credits/month','Phone numbers included','Unlimited AI outreach emails','Claude-powered email drafting','Advanced signal filtering','Dedicated account support'].map(function(f){return '
'+f+'
';}).join('')+'
' +'' +'
' +'
' // Stats +'
' +'
£45M+
Estimated total placement value inside the Vault right now
' +'
500
Seats available per country — closes permanently when full
' +'
25
Founders seats ever issued — worldwide, non-renewable
' +'
'; } // ═══ SEATS ═══ function rSeats(){ tb('Seats',''); document.getElementById('pg').innerHTML='
Active seats — 1 of 3
'+S.user.init+'
'+S.user.name+' Owner
'+S.user.name.split(' ')[0].toLowerCase()+'@company.com · '+S.user.plan+' seat · Active now
Active
+ Invite a team member
'; } // ═══ REPORTS ═══ function rReports(){ tb('Reports',''); var bars=[2,0,1,3,1,2,0,1,0,2,1,0,3,1,2,0,1,2,0,1,0,0,1,2,0,1,3,0,1,2]; document.getElementById('pg').innerHTML='
Vacancies viewed
48
+12 this week
HM contacts unlocked
'+(60-S.credits)+'
This month
Avg signal score
74
+3 vs last month
' +'
Credit consumption — April 2026
'+bars.map(function(n){return '
';}).join('')+'
1 AprToday
'; } // ═══ SETTINGS ═══ function rSettings(){ tb('Settings'); document.getElementById('pg').innerHTML='
Account
' +'
Name
'+S.user.name+'
' +'
Plan
'+S.user.plan+' Seat · '+S.user.price+'/month
' +'
Sign out
End your current session
' +'
' +'
Notifications
' +[['Hot signal alerts','Delivered instantly via email and in-app',true],['Daily vacancy digest','Summary of new matches at 9am',true],['Credit usage warnings','Alert when below 5 credits remaining',true],['Weekly report','Usage summary every Monday',false]].map(function(a){return '
'+a[0]+'
'+a[1]+'
';}).join('') +'
' +'
Integrations
' +[['Lusha','Hiring manager contact enrichment','Connected'],['Apollo.io','Backup contact data source','Not connected'],['Exa Search','Live ATS and job market signals','Connected'],['Slack','Alert notifications','Not connected']].map(function(a){return '
'+a[0]+'
'+a[1]+'
'+a[2]+'
';}).join('') +'
'; } // ═══ AUDIT ═══ function rAudit(){ tb('Audit Log',''); var logs=[['HM contact unlocked','Flint Analytics · Senior Backend Engineer','Today, 12:19','var(--am)'],['Vacancy saved','Senior Backend Engineer · Flint Analytics','Today, 12:18','var(--bl)'],['Alert created','Senior Backend Engineer · Hot only · London, UK','1 Apr, 12:00','var(--gn)'],['Live search performed','Query: FinTech backend engineer London · 12 results via Exa','31 Mar, 09:11','var(--t3)'],['Login','Chrome / macOS · IP: 82.xxx.xxx.xxx','30 Mar, 08:55','var(--gn)']]; document.getElementById('pg').innerHTML=''+logs.map(function(a){return '';}).join('')+'
EventDetailTimestamp
'+a[0]+'
'+a[1]+''+a[2]+'
'; } // ═══ MODALS ═══ function om(id){document.getElementById(id).style.display='flex';} function cm(id){document.getElementById(id).style.display='none';} function pso(el,v){document.querySelectorAll('.so').forEach(function(e){e.classList.remove('sel');});el.classList.add('sel');S.ss=v;} function saveAlert(){ var t=document.getElementById('al-t').value||document.getElementById('al-c').value; if(!t){toast('Enter a job title or company','var(--rd)');return;} ['al-t','al-c','al-l'].forEach(function(id){var el=document.getElementById(id);if(el)el.value='';}); cm('m-alert');toggleSaveSearch(t); } function shUnlock(id){ var v=S.vacs.find(function(x){return x.id===id;})||SEED.find(function(x){return x.id===id;}); if(!v){toast('Vacancy not found','var(--rd)');return;} var co=(v.co||'').trim(); var coL=co.toLowerCase(); // Known job board names — not a real employer for Lusha lookup var boardNames=['sportswork','globalsportsjobs','global sports jobs','iworkinsport', 'iwork in sport','sportyjob','ecosports','velojobs','hitmarker','sportsjobfinder', 'teamworkonline','teamwork online','uksport','uk sport','cyclingnews','sports-insight', 'jobs4football','employer tbc','sports organisation','sports organization','sports board', 'hiring company','company','direct employer','unknown','']; var isBoardName=boardNames.some(function(b){return coL.includes(b);}); if(isBoardName){ var extracted=''; // 1. Try to extract from the job TITLE — most reliable // Pattern: "Role at Employer" or "Role — Employer" or "Role | Employer" var title = v.title || ''; var titlePatterns = [ /\bat\s+([A-Z][A-Za-z0-9\s&\-\.]{2,50})$/, // "Physio at University of Oxford" /\|\s*([A-Z][A-Za-z0-9\s&\-\.]{3,50})\s*$/, // "Physio | Club Name" /[-–]\s*([A-Z][A-Za-z0-9\s&\-\.]{3,50})\s*$/, // "Physio - Club Name" /\(([A-Z][A-Za-z0-9\s&\-\.]{3,50})\)\s*$/, // "Physio (Club Name)" ]; for(var tp of titlePatterns){ var tm=title.match(tp); if(tm&&tm[1]){ var cand=tm[1].trim(); if(cand.length>3&&cand.length<60&&!boardNames.some(function(b){return cand.toLowerCase().includes(b);})){ extracted=cand; break; } } } // 2. Try from insights/description text if(!extracted){ var insightText=((v.insights||[])[0]||'')+' '+((v.sigs||[])[0]&&v.sigs[0].desc||''); var employerPatterns=[ /(?:organisation|organization|employer|club|team|company|posted by)[:\s]+([A-Z][A-Za-z0-9\s&\-\.]{2,50})/i, /([A-Z][A-Za-z0-9\s&]{3,40})\s+(?:is|are)\s+(?:looking for|hiring|recruiting|seeking)/i, /(?:join|work for|work with|working at)\s+([A-Z][A-Za-z0-9\s&\-\.]{3,50})/i, ]; for(var ep of employerPatterns){ var em=insightText.match(ep); if(em&&em[1]){ var cand=em[1].trim(); if(cand.length>3&&cand.length<60&&!boardNames.some(function(b){return cand.toLowerCase().includes(b);})){ extracted=cand; break; } } } } if(extracted){ v.co=extracted; co=extracted; }else{ // No employer found — show clear message and let user decide cm('m-unlock'); toast('No employer identified for this listing','var(--am)'); // Show a small prompt with options var jobUrl = v.url && v.url !== '#' ? v.url : null; if(jobUrl){ setTimeout(function(){ if(confirm('Employer name not found for "' + (v.title||'this role').substring(0,50) + '".\n\nOpen the job listing to find the hiring company?')){ window.open(jobUrl,'_blank'); } }, 300); } return; } } S.pu=id;document.getElementById('ul-c').textContent=S.credits;om('m-unlock'); } function doUnlock(){ if(S.credits<1){cm('m-unlock');toast('No credits remaining','var(--rd)');if(isTrialUser())showUpgradeModal('trialExpired');else go('credits');return;} var v=S.vacs.find(function(x){return x.id===S.pu;})||SEED.find(function(x){return x.id===S.pu;}); if(!v){cm('m-unlock');toast('Vacancy not found','var(--rd)');return;} var token=S.token||localStorage.getItem('vh_token');cm('m-unlock');toast('Looking up hiring manager via Lusha...'); fetch(WORKER+'/hm/unlock',{method:'POST',headers:{'Content-Type':'application/json','Authorization':'Bearer '+(token||'')},body:JSON.stringify({companyName:v.co||'',jobTitle:v.title||'',sector:v.sector||'',vacancyId:v.id||''})}) .then(function(r){return r.json();}).then(function(data){ if(data.ok&&data.hm){ S.credits=Math.max(0,S.credits-(data.charged?1:0));localStorage.setItem('vh_credits',S.credits); var realHM=data.hm;var vacIdx=S.vacs.findIndex(function(x){return x.id===S.pu;}); if(vacIdx>=0){S.vacs[vacIdx].hm={name:realHM.name||'Hiring Manager',init:(realHM.name||'HM').substring(0,2).toUpperCase(),role:realHM.title||'Hiring Manager',email:realHM.email||null,phone:realHM.phone||null,linkedin:realHM.linkedin||null,resp:75,verified:realHM.verified||false};} S.ul.add(S.pu);updCreds();toast(data.cached?'Contact retrieved — no credit used':'HM contact unlocked — 1 credit used'); if(S.page==='detail')rDetail(); }else if(data.trialExpired){showUpgradeModal('trialExpired');} else if(data.ok===false&&!data.charged){toast('No hiring manager found for this company. No credit charged.','var(--am)');} else{toast(data.error||'Could not retrieve contact data','var(--rd)');} updCreds(); }).catch(function(e){toast('Connection error: '+e.message,'var(--rd)');}); } // ═══ OUTREACH ═══ var _outreachVacId = null; var _outreachHM = null; function shOutreach(vacId) { var v = S.vacs.find(function(x){return x.id===vacId;}) || SEED.find(function(x){return x.id===vacId;}); if (!v) { toast('Vacancy not found','var(--rd)'); return; } var plan = S.user && S.user.plan; if (!['pro','pro_plus'].includes(plan)) { toast('Email outreach requires Pro or Pro+ plan','var(--am)'); return; } if (!S.ul || !S.ul.has(vacId)) { toast('Unlock the HM contact first','var(--am)'); return; } var hm = v.hm || {}; if (!hm.email) { toast('No email address available for this contact','var(--am)'); return; } _outreachVacId = vacId; _outreachHM = hm; document.getElementById('out-to').textContent = hm.name || 'Hiring Manager'; document.getElementById('out-to-email').textContent = hm.email || ''; document.getElementById('out-vac').textContent = v.title || 'Role'; document.getElementById('out-co').textContent = v.co || ''; document.getElementById('out-subject').value = ''; document.getElementById('out-body').value = ''; document.getElementById('out-status').style.display = 'none'; var limitEl = document.getElementById('out-limit'); if (limitEl) limitEl.textContent = plan === 'pro' ? '20 emails/month on Pro' : 'Unlimited on Pro+'; om('m-outreach'); draftOutreach(); } function draftOutreach() { if (!_outreachVacId) return; var v = S.vacs.find(function(x){return x.id===_outreachVacId;}) || SEED.find(function(x){return x.id===_outreachVacId;}); if (!v) return; var subjEl = document.getElementById('out-subject'); var bodyEl = document.getElementById('out-body'); var btn = document.getElementById('out-send-btn'); subjEl.value = 'Drafting...'; bodyEl.value = 'Generating your personalised intro email...'; if (btn) btn.disabled = true; var token = localStorage.getItem('vh_token'); fetch(WORKER + '/outreach/draft', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ vacancyTitle: v.title, companyName: v.co, hmName: _outreachHM && _outreachHM.name, hmTitle: _outreachHM && _outreachHM.role, recruiterName: S.user && S.user.name, agencyName: 'Syntech Recruitment', sector: v.sector }) }).then(function(r){ return r.json(); }).then(function(d) { if (d.ok) { subjEl.value = d.subject || ''; bodyEl.value = d.body || ''; if (btn) btn.disabled = false; } else if (d.upgrade) { cm('m-outreach'); toast(d.error || 'Upgrade required','var(--am)'); } else { subjEl.value = ''; bodyEl.value = ''; if (btn) btn.disabled = false; toast('AI draft failed — you can type manually','var(--am)'); } }).catch(function(e) { subjEl.value = ''; bodyEl.value = ''; if (btn) btn.disabled = false; toast('Draft error: ' + e.message,'var(--rd)'); }); } function sendOutreach() { if (!_outreachVacId || !_outreachHM) return; var v = S.vacs.find(function(x){return x.id===_outreachVacId;}) || SEED.find(function(x){return x.id===_outreachVacId;}); var subject = (document.getElementById('out-subject').value || '').trim(); var body = (document.getElementById('out-body').value || '').trim(); if (!subject || !body) { toast('Subject and body required','var(--am)'); return; } var btn = document.getElementById('out-send-btn'); var statusEl = document.getElementById('out-status'); if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; } var token = localStorage.getItem('vh_token'); fetch(WORKER + '/outreach/send', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ toEmail: _outreachHM.email, toName: _outreachHM.name, subject: subject, emailBody: body, vacancyTitle: v ? v.title : '', companyName: v ? v.co : '' }) }).then(function(r){ return r.json(); }).then(function(d) { if (d.ok) { statusEl.style.display = 'block'; statusEl.style.cssText += ';background:rgba(22,193,122,0.08);border:1px solid var(--gl);color:var(--gn)'; statusEl.textContent = String.fromCharCode(10003) + ' Email sent. Replies go directly to your registered email.'; if (btn) { btn.textContent = 'Sent ' + String.fromCharCode(10003); btn.style.opacity = '0.5'; } toast('Email sent successfully'); } else if (d.upgrade) { cm('m-outreach'); toast(d.error || 'Upgrade to Pro+ for unlimited outreach','var(--am)'); } else { if (btn) { btn.disabled = false; btn.textContent = 'Send Email'; } toast(d.error || 'Send failed','var(--rd)'); } }).catch(function(e) { if (btn) { btn.disabled = false; btn.textContent = 'Send Email'; } toast('Send error: ' + e.message,'var(--rd)'); }); } function updCreds(){ var plan=S.user&&S.user.plan?S.user.plan:'trial'; var tot=plan==='pro_plus'?75:plan==='pro'?50:plan==='founders'?25:5; var hmC=typeof S.credits==='number'?S.credits:tot;var specC=typeof S.specCredits==='number'?S.specCredits:tot; var sbCt=document.getElementById('sb-ct');if(sbCt)sbCt.textContent=hmC+' HM credits'; var sbSc=document.getElementById('sb-sc');if(sbSc)sbSc.textContent=specC+' Spec Match credits'; var hmBar=document.getElementById('sb-ct-bar');if(hmBar)hmBar.style.width=Math.min(100,Math.round(hmC/tot*100))+'%'; var scBar=document.getElementById('sb-sc-bar');if(scBar)scBar.style.width=Math.min(100,Math.round(specC/tot*100))+'%'; updateTrialBadge(); } function toast(msg,col){ var t=document.getElementById('toast');document.getElementById('td').style.background=col||'var(--gn)';document.getElementById('tm').textContent=msg; t.classList.add('show');clearTimeout(t._t);t._t=setTimeout(function(){t.classList.remove('show');},2800); } function doSignOut(){ var token=S.token||localStorage.getItem('vh_token'); if(token){fetch(WORKER+'/auth/logout',{method:'POST',headers:{'Authorization':'Bearer '+token}}).catch(function(){});} localStorage.removeItem('vh_token');localStorage.removeItem('vh_user');localStorage.removeItem('vh_credits'); S.token=null;S.vacs=[];S.saved=new Set();S.ul=new Set();S.savedSearches=[];_savedVacsCache={};_savedSearchesCache=[]; showAuth('login'); } // ═══ AUTH TAB / COUNTRY / SIGNAL FEED ═══ function authTab(tab,btn){ document.querySelectorAll('.auth-tab').forEach(function(b){b.classList.remove('on');});btn.classList.add('on'); ['signals','spec','hm'].forEach(function(t){var p=document.getElementById('auth-panel-'+t);if(p)p.style.display='none';}); var panel=document.getElementById('auth-panel-'+tab); if(panel){panel.style.display='block';panel.style.opacity='0';panel.style.transform='translateY(6px)';setTimeout(function(){panel.style.transition='opacity 0.25s ease,transform 0.25s ease';panel.style.opacity='1';panel.style.transform='translateY(0)';},10);} if(tab==='signals'&&typeof startSignalFeed==='function')startSignalFeed(); } var COUNTRY_DATA={gb:{name:'United Kingdom',flag:'GB',agencies:30000,label:'recruitment businesses'},au:{name:'Australia',flag:'AU',agencies:10000,label:'recruitment agencies'},us:{name:'United States',flag:'US',agencies:20000,label:'staffing firms'},ie:{name:'Ireland',flag:'IE',agencies:2000,label:'recruitment agencies'},ca:{name:'Canada',flag:'CA',agencies:8000,label:'staffing firms'},nz:{name:'New Zealand',flag:'NZ',agencies:1500,label:'recruitment agencies'},za:{name:'South Africa',flag:'ZA',agencies:3000,label:'recruitment agencies'},sg:{name:'Singapore',flag:'SG',agencies:2500,label:'recruitment agencies'},ae:{name:'UAE',flag:'AE',agencies:2000,label:'recruitment agencies'},de:{name:'Germany',flag:'DE',agencies:9000,label:'staffing agencies'}}; function onCountryChange(){ var sel=document.getElementById('su-country'),intel=document.getElementById('su-country-intel'),sub=document.getElementById('su-usp-sub'); if(!sel||!intel)return;var code=sel.value;if(!code){intel.style.display='none';return;} var d=COUNTRY_DATA[code];if(!d){intel.style.display='none';return;} var af=d.agencies.toLocaleString()+'+',fp=((25/d.agencies)*100).toFixed(2); intel.style.display='block'; intel.innerHTML='
'+d.flag+''+d.name+'
' +'
'+af+'
'+d.label+'
' +'
25
Founders globally
' +'
Top '+fp+'%
of '+d.name+'
' +'
There are '+af+' '+d.label+' in '+d.name+'. Only 25 Founders seats globally. You are in the top '+fp+'% of your market.
'; intel.style.opacity='0';intel.style.transform='translateY(6px)'; setTimeout(function(){intel.style.transition='opacity 0.3s ease,transform 0.3s ease';intel.style.opacity='1';intel.style.transform='translateY(0)';},20); if(sub)sub.textContent='Top '+fp+'% of '+d.name+' recruiters. Only 25 Founders seats globally.'; } var _signalRefreshTimer=null; function fetchLiveSignals(){ var container=document.getElementById('auth-feed-items');if(!container)return; fetch(WORKER+'/public/signals').then(function(r){return r.json();}).then(function(data){ var signals=data.signals||[];if(!signals.length||!document.getElementById('auth-feed-items'))return; container.innerHTML=''; signals.slice(0,5).forEach(function(sig,i){ var sc=sig.sig==='HOT'?'hot':sig.sig==='NEW'?'new':'warm'; var item=document.createElement('div');item.className='auth-feed-item entering'; item.innerHTML='
'+sig.sig+''+(sig.score||80)+'
'+(sig.title||'Role')+'
'+(sig.loc||'UK')+' · Direct employer
'+(sig.sal||'Competitive')+'
'; container.appendChild(item); setTimeout(function(){item.classList.remove('entering');item.classList.add('entered');},i*120+50); }); }).catch(function(){}); } function startSignalFeed(){ fetchLiveSignals();if(_signalRefreshTimer)clearInterval(_signalRefreshTimer); _signalRefreshTimer=setInterval(function(){if(document.getElementById('auth-sc')&&document.getElementById('auth-sc').style.display!=='none'){fetchLiveSignals();}else{clearInterval(_signalRefreshTimer);_signalRefreshTimer=null;}},90000); } // ═══ LOGIN / SIGNUP ═══ function doLogin(){ var e=document.getElementById('li-e').value.trim(),p=document.getElementById('li-p').value,err=document.getElementById('li-err'); if(!e||!p){err.classList.add('show');return;}err.classList.remove('show'); var btn=document.querySelector('.a-btn.a-green');if(btn){btn.disabled=true;btn.textContent='Signing in...';} fetch(WORKER+'/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:e,password:p})}) .then(function(r){return r.json();}).then(function(data){ if(data.ok){S.token=data.session.token;S.credits=data.session.credits||25;S.user={name:data.session.name,init:(data.session.name||'U').split(' ').map(function(w){return w[0];}).join('').substring(0,2).toUpperCase(),plan:data.session.plan,price:data.session.plan==='pro'?'£99':data.session.plan==='trial'?'':'£59'};localStorage.setItem('vh_token',S.token);localStorage.setItem('vh_user',JSON.stringify(S.user));localStorage.setItem('vh_credits',S.credits);launchApp();} else{err.classList.add('show');err.textContent=data.error||'Invalid email or password.';if(btn){btn.disabled=false;btn.textContent='Sign in to VaultHire';}} }).catch(function(){err.classList.add('show');err.textContent='Connection error. Please try again.';if(btn){btn.disabled=false;btn.textContent='Sign in to VaultHire';}}); } async function doSignup(){ var fn=(document.getElementById('su-fn')||{}).value||'',ln=(document.getElementById('su-ln')||{}).value||'',em=(document.getElementById('su-e')||{}).value||'',pw=(document.getElementById('su-p')||{}).value||''; var country=(document.getElementById('su-country')||{}).value||'gb'; var errEl=document.getElementById('su-err'); if(!fn.trim()||!ln.trim()||!em.trim()||!pw){if(errEl){errEl.style.display='block';errEl.textContent='Please fill in all fields.';}return;} if(pw.length<8){if(errEl){errEl.style.display='block';errEl.textContent='Password must be at least 8 characters.';}return;} var btn=document.querySelector('.a-btn.a-green');if(btn){btn.textContent='Creating...';btn.disabled=true;} try{ var resp=await fetch(WORKER+'/auth/register',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:fn.trim()+' '+ln.trim(),email:em.trim().toLowerCase(),password:pw,plan:selPlan||'trial',currency:'GBP',country:country})}); var data=await resp.json(); if(!data.ok){if(errEl){errEl.style.display='block';errEl.textContent=data.error||'Registration failed.';}if(btn){btn.textContent='Start free trial →';btn.disabled=false;}return;} S.token=data.session.token;S.user={name:data.session.name,init:(data.session.name||'').split(' ').map(function(w){return w[0]||'';}).join('').substring(0,2).toUpperCase(),plan:data.session.plan,price:data.session.plan==='pro_plus'?'£139':data.session.plan==='pro'?'£99':'£59'}; S.credits=data.session.credits;S.specCredits=data.session.credits; localStorage.setItem('vh_token',S.token);localStorage.setItem('vh_user',JSON.stringify(S.user));localStorage.setItem('vh_credits',String(S.credits)); launchApp(); }catch(e){if(errEl){errEl.style.display='block';errEl.textContent='Connection error. Please try again.';}if(btn){btn.textContent='Start free trial →';btn.disabled=false;}} } // ═══ SESSION RESTORE ═══ (function(){ var token=localStorage.getItem('vh_token'),user=localStorage.getItem('vh_user'),credits=localStorage.getItem('vh_credits'); if(token&&user){ try{ S.token=token;S.user=JSON.parse(user); var plan=S.user.plan||'trial'; S.credits=parseInt(credits)||(plan==='pro_plus'?75:plan==='pro'?50:plan==='founders'?25:5);S.specCredits=S.credits; fetch(WORKER+'/auth/me',{headers:{'Authorization':'Bearer '+token}}) .then(function(r){return r.json();}).then(function(data){ if(data.ok&&data.session){S.credits=data.session.credits;S.user.plan=data.session.plan;if(data.session.plan==='trial_expired'){S.token=null;localStorage.removeItem('vh_token');showAuth('login');setTimeout(function(){toast('Your free trial has ended.','var(--rd)');},500);return;}launchApp();} else{localStorage.removeItem('vh_token');localStorage.removeItem('vh_user');localStorage.removeItem('vh_credits');showAuth('login');} }).catch(function(){launchApp();}); }catch(e){showAuth('login');} }else{showAuth('login');} if(typeof startSignalFeed==='function')startSignalFeed(); })();