🧬

Your DNA Already Knows Things About Your Future

Upload nearly any genetic test and enter a personalized AI experience built around your variants, pathways, and biological tendencies. Health intelligence before care is needed.

v13.2248
23andMe · AncestryDNA · VCF · PDF 2,900+ studies AI-powered analysis
Privacy · Terms · Educational use only
🦉 HYPATIA · GENE CHAT
Context-aware — I know your variant profile
Hey! I'm Hypatia. Upload your genetic test data and I can walk you through what your variants mean, what supplements to focus on, and build a personalized report. What do you wanna explore?

Geneius Report

Generated ${new Date().toLocaleDateString()}

${viewer.innerHTML}`; const blob=new Blob([html],{type:'text/html'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='geneius-report-'+Date.now()+'.html';a.click();URL.revokeObjectURL(a.href);toast('Report downloaded!'); } async function emailReport(){ if(!currentReport?.token)return toast('Generate a report first',false); const to=prompt('Send report to email address:'); if(!to||!to.includes('@'))return; toast('Sending...'); try{const d=await API('/devlab/gene/report/email',{token:currentReport.token,to});if(d.ok)toast('Report sent to '+to);else toast(d.error||'Send failed',false)}catch(e){toast('Failed: '+e.message,false)} } function renderMd(raw){ if(!raw) return ''; let s = raw.replace(/&/g,'&').replace(//g,'>'); s = s.replace(/\*\*([^*\n]+)\*\*/g, '$1'); s = s.replace(/\*([^*\n]+)\*/g, '$1'); s = s.replace(/`([^`\n]+)`/g, '$1'); s = s.replace(/^### (.+)$/gm, '

$1

'); s = s.replace(/^## (.+)$/gm, '

$1

'); s = s.replace(/^# (.+)$/gm, '

$1

'); s = s.replace(/\n/g, '
'); return s; } // ══════════════════════════════════════════════════════════════════════════════ // Admin Spreadsheet — Card CRUD // ══════════════════════════════════════════════════════════════════════════════ const ADMIN_FIELDS=[ {key:'gene_symbol',label:'Gene'},{key:'rsid',label:'rsID'},{key:'common_name',label:'Common Name'},{key:'category',label:'Category'}, {key:'actionability_tier',label:'Tier'},{key:'scientific_confidence',label:'Confidence'}, {key:'env',label:'Env',type:'select',opts:['staging','production','sandbox']}, {key:'status',label:'Status',type:'select',opts:['draft','in_research','needs_review','adjudication_ready','approved_staging','approved_production','archived']}, {key:'supplements',label:'Supplements',wide:true},{key:'diet_notes',label:'Diet/Lifestyle',wide:true}, {key:'safety_note',label:'Safety Note',wide:true},{key:'evidence_summary_short',label:'Evidence Summary',wide:true}, {key:'mechanism_summary',label:'Mechanism',wide:true} ]; async function loadAdminCards(){ const env=document.getElementById('admin-env').value; try{const d=await API('/devlab/gene/card/list',{env});if(d.ok){adminCards=d.cards||[];renderAdminTable()}} catch(e){toast('Failed to load: '+e.message,false)} } function renderAdminTable(filter=''){ const tbody=document.getElementById('admin-tbody'); let cards=adminCards; if(filter){const f=filter.toLowerCase();cards=cards.filter(c=>Object.values(c).some(v=>String(v).toLowerCase().includes(f)))} document.getElementById('admin-count').textContent=`${cards.length} cards`; tbody.innerHTML=cards.map(c=>{ const isE=editingRow===c.record_id; return`
${isE ?`` :`` }
${ADMIN_FIELDS.map(f=>{ const val=c[f.key]||''; if(isE){ if(f.type==='select')return``; return``; } if(f.key==='env')return`${esc(val)}`; if(f.key==='status')return`${esc(val)}`; return`${esc(String(val).slice(0,120))}${String(val).length>120?'...':''}`; }).join('')}`; }).join(''); } function editCard(rid){editingRow=rid;renderAdminTable(document.getElementById('admin-search').value);setTimeout(()=>{const r=document.querySelector(`tr[data-rid="${rid}"]`);if(r)r.querySelector('.cell-input')?.focus()},50)} function cancelEdit(){editingRow=null;renderAdminTable(document.getElementById('admin-search').value)} async function saveCard(rid){ const row=document.querySelector(`tr[data-rid="${rid}"]`);if(!row)return; const card=adminCards.find(c=>c.record_id===rid);if(!card)return; const updated={...card}; row.querySelectorAll('.cell-input').forEach(inp=>{const f=inp.dataset.field;if(f)updated[f]=inp.value}); try{ const d=await API('/devlab/gene/card/save',{card:updated}); if(d.ok){const idx=adminCards.findIndex(c=>c.record_id===rid);if(idx>=0)adminCards[idx]=updated;editingRow=null;renderAdminTable(document.getElementById('admin-search').value);toast(`Saved ${updated.gene_symbol} ${updated.rsid}`);if(allCards.length>0)loadCards()} else toast('Save failed: '+(d.error||''),false); }catch(e){toast('Save failed: '+e.message,false)} } async function deleteCard(rid){ const card=adminCards.find(c=>c.record_id===rid); if(!confirm(`Delete ${card?.gene_symbol} ${card?.rsid}? Cannot be undone.`))return; try{const d=await API('/devlab/gene/card/delete',{record_id:rid});if(d.ok){adminCards=adminCards.filter(c=>c.record_id!==rid);renderAdminTable(document.getElementById('admin-search').value);toast(`Deleted ${card?.gene_symbol}`);if(allCards.length>0)loadCards()}else toast('Delete failed: '+(d.error||''),false)} catch(e){toast('Delete failed: '+e.message,false)} } async function addNewCard(){ const gene=prompt('Gene symbol (e.g. MTHFR):');if(!gene)return;const rsid=prompt('rsID (e.g. rs1801133):'); const nc={gene_symbol:gene,rsid:rsid||'',common_name:'',category:'',actionability_tier:'4',scientific_confidence:'D',env:'staging',status:'draft',inferred_phenotype:'',mechanism_summary:'',safety_note:'',supplements:'',diet_notes:'',peptide_badge:'',evidence_summary_short:'TBD / needs curation',source_links:'',treatment_sources:''}; try{const d=await API('/devlab/gene/card/save',{card:nc});if(d.ok){toast(`Created card for ${gene}`);loadAdminCards()}else toast('Create failed: '+(d.error||''),false)}catch(e){toast('Create failed: '+e.message,false)} } function filterAdmin(){renderAdminTable(document.getElementById('admin-search').value)} async function promoteCard(rid){ const card=adminCards.find(c=>c.record_id===rid);if(!card)return; const env=card.env||'staging'; const next=env==='sandbox'?'staging':env==='staging'?'production':null; if(!next)return toast('Already in production',false); if(!confirm(`Promote ${card.gene_symbol} from ${env} to ${next}?`))return; try{const d=await API('/devlab/gene/card/promote',{record_id:rid,target_env:next});if(d.ok){toast(`${card.gene_symbol} promoted to ${next}`);loadAdminCards();if(allCards.length>0)loadCards()}else toast(d.error||'Failed',false)}catch(e){toast('Failed: '+e.message,false)} } // Session persistence let sessionId=null; async function saveSession(){ const session={variants:uploadedMatched,clusters:uploadedClusters,interview:interviewHistory,timestamp:new Date().toISOString()}; try{const d=await API('/devlab/gene/session/save',{session});if(d.ok)sessionId=d.session_id}catch(e){} } // Adaptive interview via /devlab/gene/interview let interviewMode='intake'; async function startInterview(){ if(!uploadedMatched.length)return; try{ const d=await API('/devlab/gene/interview',{variants:uploadedMatched.slice(0,20),history:[],profile:{},clusters:uploadedClusters}); if(d.ok&&d.question){ let msg=d.question; if(d.reason)msg+=`
Why I'm asking \u25be
${esc(d.reason)}
`; addMsg('ai',msg); interviewMode=d.done?'exploration':'phenotype'; } }catch(e){} } // Learning loop — save completed case after report generation async function saveLearningCase(){ if(!currentReport?.token||!uploadedMatched.length)return; const caseData={token:currentReport.token,variants:uploadedMatched,clusters:uploadedClusters,interview:interviewHistory,completed_at:new Date().toISOString()}; try{await API('/devlab/gene/session/save',{session:{...caseData,session_id:sessionId||undefined,type:'completed_case'}})}catch(e){} } // Research Queue let rqItems=[]; async function loadResearchQueue(){ try{const d=await API('/devlab/gene/research-queue',{});if(d.ok){rqItems=d.queue||[];renderRQ()}}catch(e){} } function renderRQ(){ const el=document.getElementById('rq-list'); if(!rqItems.length){el.innerHTML='
No items in research queue
';return} el.innerHTML=rqItems.map(r=>`
${esc(r.gene||r.gene_symbol||'\u2014')}${esc(r.rsid||'')} ${esc(r.notes||r.reason||'')}${esc(r.status||'queued')}
`).join(''); } async function addToQueue(){ const gene=prompt('Gene symbol:');if(!gene)return; const rsid=prompt('rsID (optional):'); const notes=prompt('Research notes/reason:'); try{ const d=await API('/devlab/gene/research-queue/add',{item:{gene_symbol:gene,gene,rsid:rsid||'',notes:notes||'',reason:notes||''}}); if(d.ok){toast('Added '+gene+' to queue');loadResearchQueue()}else toast(d.error||'Failed',false); }catch(e){toast('Failed: '+e.message,false)} } // Chat let chatOpen=false,chatBusy=false; function toggleChat(){chatOpen=!chatOpen;document.getElementById('hyp-panel').classList.toggle('open',chatOpen);if(chatOpen)setTimeout(()=>document.getElementById('hyp-input')?.focus(),100)} function addMsg(role,text){const msgs=document.getElementById('hyp-msgs');const div=document.createElement('div');div.className=`hyp-msg ${role}`;div.innerHTML=text.replace(/\n/g,'
').replace(/\*\*(.+?)\*\*/g,'$1');msgs.appendChild(div);msgs.scrollTop=msgs.scrollHeight} function buildCtx(){const p=[];if(uploadedMatched.length){const hom=uploadedMatched.filter(v=>v.homozygous).map(v=>`${v.gene} ${v.rsid} (${v.genotype})`);if(hom.length)p.push('Homozygous: '+hom.join(', '));const het=uploadedMatched.filter(v=>!v.homozygous).slice(0,10).map(v=>`${v.gene} ${v.rsid}`);if(het.length)p.push('Heterozygous: '+het.join(', '))}return p.join('\n')} async function chatSend(){ if(chatBusy)return;const inp=document.getElementById('hyp-input');const msg=inp.value.trim();if(!msg)return;inp.value='';chatBusy=true;addMsg('user',msg); const ctx=buildCtx(),msgs=document.getElementById('hyp-msgs');const dots=document.createElement('div');dots.className='hyp-dots';dots.id='hyp-dots';dots.innerHTML='';msgs.appendChild(dots);msgs.scrollTop=msgs.scrollHeight; // Use adaptive interview if we have uploaded data, otherwise general chat if(uploadedMatched.length>0&&interviewMode!=='exploration'){ try{const d=await API('/devlab/gene/interview',{variants:uploadedMatched.slice(0,20),history:interviewHistory,profile:{},clusters:uploadedClusters});dots.remove(); if(d.ok){ interviewHistory.push({question:d.question||'',answer:msg}); let reply=d.question||'Thanks! I have enough info for your report now.'; if(d.reason)reply+=`
Why I asked \u25be
${esc(d.reason)}
`; if(d.done){reply+='
Interview complete! Go to the Report tab to generate your personalized report.
';interviewMode='exploration'} addMsg('ai',reply);saveSession(); }else{interviewHistory.push({question:'',answer:msg});addMsg('ai','Let me think about that...');} }catch(e){dots.remove();interviewHistory.push({question:'',answer:msg});addMsg('ai','Connection issue — try again.')} }else{ // Check for lab commands first const ml=msg.toLowerCase(); const labCmd= (ml.includes('run discovery')||ml.includes('discover new'))?'discover': (ml.includes('audit')||ml.includes('check cards'))?'audit': (ml.includes('enrich')||ml.includes('fill cards')||ml.includes('update cards'))?'enrich': (ml.includes('harvest')||ml.includes('collect studies')||ml.includes('download studies'))?'harvest': (ml.includes('lab queue')||ml.includes('pending discoveries'))?'labqueue': (ml.includes('grade')||ml.includes('regrade')||ml.includes('re-grade'))?'grade': (ml.includes('full cycle')||ml.includes('run everything'))?'full-cycle': (ml.includes('db stats')||ml.includes('database stats')||ml.includes('study count'))?'stats': (ml.includes('interest')||ml.includes('cool findings'))?'interests':null; if(labCmd){ dots.remove(); if(labCmd==='labqueue'){ showAdminSub('labqueue');document.querySelector('[data-tab="admin"]').click(); addMsg('ai','Opening the Lab Queue for you! Check the Admin tab.');chatBusy=false;return; } addMsg('sys',`Running lab command: ${labCmd}...`); const agent=(['harvest','stats','interests'].includes(labCmd))?'research_harvester_v1':'gene_lab_curator_v1'; const action=labCmd==='harvest'?'harvest':labCmd; try{ const d=await API('/devlab/ptt/run-agent-step',{agent,action,task:'',viewer_role:'admin',mode:'full_operator'}); let reply=d.ok?'Done! ':'Failed. '; if(d.output_parsed){ const p=d.output_parsed; if(labCmd==='audit')reply+=`${p.total||0} cards: ${p.tbd?.length||0} TBD, ${p.incomplete?.length||0} incomplete, ${p.well_curated||0} curated.`; else if(labCmd==='discover')reply+=`Found ${p.gene_count||0} new genes + ${p.peptide_count||0} new peptides. Check Lab Queue!`; else if(labCmd==='stats')reply+=`${p.total_studies||0} studies in DB. ${p.gene_topics||0} gene topics, ${p.peptide_topics||0} peptide topics. ${p.pending_alerts||0} alerts pending.`; else reply+=JSON.stringify(p).slice(0,200); }else reply+=(d.output||'').slice(0,200); addMsg('ai',reply); }catch(e){addMsg('ai','Lab command failed: '+e.message)} chatBusy=false;return; } // ALL messages go through research — Hypatia always has her lab team on call try{ const d=await API('/devlab/gene/research',{query:msg});dots.remove(); if(d.ok&&(d.answer||d.local_cards?.length||d.cited_studies?.length)){ let reply=d.answer||''; // Cited studies from local research DB if(d.cited_studies?.length){ reply+='
'; reply+='
\ud83d\udcda Cited Studies
'; for(const s of d.cited_studies.slice(0,5))reply+=`
\u00b7 ${s.title?.slice(0,90)} ${s.authors?.split(';')[0]||''}, ${s.journal||''} ${s.pubdate||''}
`; reply+='
'; } // PubMed live results if(d.pubmed?.articles?.length){ reply+='
'; reply+='PubMed: '; for(const a of d.pubmed.articles.slice(0,3))reply+=`\u00b7 ${a.title?.slice(0,60)} `; reply+='
'; } // Knowledge base matches if(d.local_cards?.length)reply+=`
\ud83e\uddec ${d.local_cards.length} matching cards in knowledge base
`; // Sources footer if(d.sources?.length)reply+=`
Sources: ${d.sources.join(' \u00b7 ')}
`; if(!reply)reply='Hmm, I didn\'t find much on that. Try asking about a specific gene (like MTHFR), variant (rs1801133), or peptide (BPC-157)!'; addMsg('ai',reply);interviewHistory.push({question:msg,answer:reply}); }else{ // Fallback to general chat if research returned nothing dots.remove(); try{const r=await fetch('/api/ideas/chat',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'include',body:JSON.stringify({message:msg+(ctx?`\n\n[Gene context: ${ctx.slice(0,400)}]`:''),mode:'brain_chat',context_note:'Gene Console V2. '+(ctx?ctx.slice(0,200):'No upload yet.')})});const data=await r.json();addMsg('ai',data?.reply||data?.response||data?.message||'Connection issue.');interviewHistory.push({question:msg,answer:data?.reply||''})} catch(e2){addMsg('ai','Connection issue \u2014 try again.')} } }catch(e){dots.remove();addMsg('ai','Connection issue \u2014 try again.')} } chatBusy=false; } // Admin gating + sub-tabs async function checkAdminAccess(){ try{ const d=await fetch('/api/ideas/auth/whoami',{credentials:'include'}).then(r=>r.json()); const role=d?.viewer?.role||'user'; if(role==='admin'||role==='super_admin'){document.getElementById('admin-content').style.display='flex';document.getElementById('admin-gate').style.display='none'} else{document.getElementById('admin-content').style.display='none';document.getElementById('admin-gate').style.display='block'} }catch(e){} } function showAdminSub(id){ document.querySelectorAll('.admin-sub').forEach(s=>s.style.display='none'); document.getElementById('sub-'+id).style.display=id==='cards'?'flex':'block'; document.querySelectorAll('[id^=asub-]').forEach(b=>b.style.fontWeight='600'); document.getElementById('asub-'+id).style.fontWeight='800'; if(id==='missions')loadMissions(); if(id==='automation')loadDbStats(); } // Lab Team direct agent calls function labChat(agent){document.getElementById('lab-agent-select').value=agent;showAdminSub('labteam')} async function runLabAgent(){ const agent=document.getElementById('lab-agent-select').value; const action=document.getElementById('lab-action-select').value; const task=document.getElementById('lab-task').value.trim(); if(!task){toast('Enter a gene or query',false);return} document.getElementById('lab-output').textContent='Running '+agent+' --action '+action+'...'; try{ const d=await API('/devlab/ptt/run-agent-step',{agent,action,task,viewer_role:'admin',mode:'full_operator'}); document.getElementById('lab-output').textContent=d.output_parsed?JSON.stringify(d.output_parsed,null,2):d.output||JSON.stringify(d,null,2); }catch(e){document.getElementById('lab-output').textContent='Error: '+e.message} } // Curator + Harvester buttons async function runCurator(action){ const btn=document.getElementById('btn-curator');if(btn){btn.disabled=true;btn.textContent='Running...'} document.getElementById('curator-status').textContent='Running '+action+'...'; toast('Curator '+action+' started...'); try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'gene_lab_curator_v1',action,task:'',viewer_role:'admin',mode:'full_operator'}); document.getElementById('curator-status').textContent=d.ok?'Done! Check output below.':'Error: '+(d.error||'failed'); if(d.output_parsed)document.getElementById('lab-output').textContent=JSON.stringify(d.output_parsed,null,2); toast(d.ok?'Curator '+action+' complete':'Curator failed',d.ok); }catch(e){document.getElementById('curator-status').textContent='Error: '+e.message;toast('Failed',false)} if(btn){btn.disabled=false;btn.textContent='Run Full Cycle Now'} } async function runHarvester(action){ const btn=document.getElementById('btn-harvest');if(btn&&action==='harvest'){btn.disabled=true;btn.textContent='Harvesting...'} document.getElementById('harvest-status').textContent='Running '+action+'...'; toast('Harvester '+action+' started...'); try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'research_harvester_v1',action,task:'',viewer_role:'admin',mode:'full_operator'}); if(action==='stats'&&d.output_parsed)document.getElementById('db-stats').innerHTML=formatStats(d.output_parsed); else document.getElementById('harvest-status').textContent=d.ok?'Done!':'Error'; toast(d.ok?'Harvester '+action+' complete':'Failed',d.ok); }catch(e){toast('Failed',false)} if(btn&&action==='harvest'){btn.disabled=false;btn.textContent='Run Full Harvest Now'} } function formatStats(d){ if(!d)return'No data'; return`Studies: ${d.total_studies||0} | Genes: ${d.gene_topics||0} (${d.gene_studies||0} papers) | Peptides: ${d.peptide_topics||0} (${d.peptide_studies||0} papers) | Recent 24h: ${d.recent_24h||0} | Alerts: ${d.pending_alerts||0}`; } async function loadDbStats(){ try{const d=await API('/devlab/ptt/run-agent-step',{agent:'research_harvester_v1',action:'stats',task:'',viewer_role:'admin',mode:'full_operator'}); if(d.output_parsed)document.getElementById('db-stats').innerHTML=formatStats(d.output_parsed); }catch(e){} } // Research Missions const MISSIONS_KEY='pg_research_missions'; function loadMissionsData(){try{return JSON.parse(localStorage.getItem(MISSIONS_KEY)||'[]')}catch{return[]}} function saveMissionsData(m){localStorage.setItem(MISSIONS_KEY,JSON.stringify(m))} async function createMission(){ const title=document.getElementById('rm-title').value.trim(); const objective=document.getElementById('rm-objective').value.trim(); const type=document.getElementById('rm-type').value; const targets=document.getElementById('rm-targets').value.trim(); if(!title||!objective){toast('Title and objective required',false);return} const mission={id:'RM-'+Date.now(),title,objective,type,targets:targets.split(',').map(s=>s.trim()).filter(Boolean),status:'active',created:new Date().toISOString(),steps:[],results:[]}; // Also submit as proposal try{await API('/missions/propose',{title:'[Lab Mission] '+title,summary:objective,category:'Gene Lab',tags:['research','lab_mission']});}catch(e){} const missions=loadMissionsData();missions.unshift(mission);saveMissionsData(missions); document.getElementById('rm-title').value='';document.getElementById('rm-objective').value='';document.getElementById('rm-targets').value=''; toast('Mission launched: '+title);loadMissions(); // Auto-run first research step if(mission.targets.length>0){ for(const t of mission.targets.slice(0,3)){ try{await API('/devlab/gene/research',{query:t+' '+objective.slice(0,50)})}catch(e){} } toast('Initial research dispatched for '+mission.targets.slice(0,3).join(', ')); } } function loadMissions(){ const el=document.getElementById('missions-list');if(!el)return; const missions=loadMissionsData(); if(!missions.length){el.innerHTML='
No active missions. Create one above.
';return} el.innerHTML=missions.map(m=>{ const stColor={'active':'var(--pg-success)','completed':'var(--pg-info)','paused':'var(--pg-warning)'}[m.status]||'var(--pg-text-dim)'; return`
${esc(m.title)}
${esc(m.status)}
${esc(m.objective?.slice(0,120))}
${esc(m.type)} · Targets: ${esc((m.targets||[]).join(', ')||'general')} · ${new Date(m.created).toLocaleDateString()}
`; }).join(''); } async function runMissionResearch(id){ const missions=loadMissionsData();const m=missions.find(x=>x.id===id);if(!m)return; toast('Dispatching research for: '+m.title); const targets=m.targets.length>0?m.targets:['gene variant actionable']; for(const t of targets.slice(0,5)){ try{await API('/devlab/gene/research',{query:t+' '+m.objective?.slice(0,60)})}catch(e){} } toast('Research dispatched!'); } function completeMission(id){const m=loadMissionsData();const i=m.findIndex(x=>x.id===id);if(i>=0){m[i].status='completed';saveMissionsData(m);loadMissions();toast('Mission completed')}} function deleteMission(id){if(!confirm('Delete this mission?'))return;const m=loadMissionsData().filter(x=>x.id!==id);saveMissionsData(m);loadMissions();toast('Mission deleted')} // Lab Queue async function loadLabQueue(status){ const el=document.getElementById('labqueue-list');if(!el)return; el.innerHTML='
Loading...
'; try{ const d=await API('/devlab/ptt/run-agent-step',{agent:'gene_lab_curator_v1',action:'audit',task:'',viewer_role:'admin',mode:'full_operator'}); // Load queue file directly via research endpoint hack }catch(e){} // Use localStorage as bridge since we can't directly read the queue file from frontend // Instead, call discover which populates and returns the queue try{ const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); renderLabQueue(items.filter(i=>!status||i.status===status)); }catch(e){el.innerHTML='
Run "Discovery" first to populate the queue.
'} } function renderLabQueue(items){ const el=document.getElementById('labqueue-list'); if(!items.length){el.innerHTML='
No items in queue. Run "Run Discovery Now" to find new genes and peptides.
';return} el.innerHTML=items.map(q=>{ const typeColor=q.type==='gene'?'var(--pg-primary)':'var(--pg-info)'; const stColor={'pending':'var(--pg-warning)','approved':'var(--pg-success)','rejected':'var(--pg-danger)','card_created':'var(--pg-primary)'}[q.status]||'var(--pg-text-dim)'; return`
${esc(q.type)} ${esc(q.name)}
${esc(q.status)}
${esc(q.reason||'')}
${esc(q.submitted_by||'')} · ${q.submitted_at?new Date(q.submitted_at).toLocaleDateString():''}
${q.status==='pending'?`
`:''}
`; }).join(''); } async function approveLabItem(id,type,name){ // Update status in localStorage queue const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); const idx=items.findIndex(i=>i.id===id); if(idx>=0){items[idx].status='approved';items[idx].reviewed_at=new Date().toISOString();localStorage.setItem('pg_lab_queue',JSON.stringify(items))} // Create a card for it if(type==='gene'){ const nc={gene_symbol:name,rsid:'',common_name:'',category:'',actionability_tier:'4',scientific_confidence:'D',env:'staging',status:'draft',inferred_phenotype:'',mechanism_summary:'',safety_note:'',supplements:'',diet_notes:'',peptide_badge:'',evidence_summary_short:'TBD / needs curation',source_links:'',treatment_sources:''}; await API('/devlab/gene/card/save',{card:nc}); toast('Card created for '+name+' — will be enriched on next curator cycle'); }else{ toast('Approved: '+name+' — add to peptide KB manually or via lab team'); } loadLabQueue('pending'); } function rejectLabItem(id){ const items=JSON.parse(localStorage.getItem('pg_lab_queue')||'[]'); const idx=items.findIndex(i=>i.id===id); if(idx>=0){items[idx].status='rejected';items[idx].reviewed_at=new Date().toISOString();localStorage.setItem('pg_lab_queue',JSON.stringify(items))} toast('Rejected');loadLabQueue('pending'); } async function researchLabItem(name){ toast('Researching '+name+'...'); const d=await API('/devlab/gene/research',{query:name+' mechanism therapeutic actionable'}); if(d.ok&&d.answer){addMsg('ai',d.answer);if(!chatOpen)toggleChat()} else toast('Research returned no results',false); } // Init // Check if returning user — skip splash if(sessionStorage.getItem('pg_entered')){document.getElementById('splash').classList.add('hidden');document.getElementById('main-app').style.display='flex';loadPageGate()} loadCards(); // ── DNA Double Helix Splash — full-screen, responsive, mouse-reactive ── (function(){ const c=document.getElementById('splash-canvas');if(!c)return; const ctx=c.getContext('2d'); let W,H,mx=-999,my=-999,dpr=1; // Responsive sizing — use window dimensions, apply devicePixelRatio for retina function resize(){ dpr=Math.min(window.devicePixelRatio||1,2); W=window.innerWidth; H=window.innerHeight; c.width=W*dpr; c.height=H*dpr; c.style.width=W+'px'; c.style.height=H+'px'; ctx.setTransform(dpr,0,0,dpr,0,0); } resize(); window.addEventListener('resize',resize); // Mouse / touch tracking window.addEventListener('mousemove',e=>{mx=e.clientX;my=e.clientY}); window.addEventListener('mouseleave',()=>{mx=-999;my=-999}); window.addEventListener('touchmove',e=>{ if(e.touches[0]){mx=e.touches[0].clientX;my=e.touches[0].clientY;} },{passive:true}); window.addEventListener('touchend',()=>{mx=-999;my=-999}); // Breakpoint-aware settings function cfg(){ if(W<=480) return{helixR:W*.22,nodes:28,nodeR:2.8,rungs:14,speed:.0006,repR:90,bgParts:40}; if(W<=768) return{helixR:W*.2,nodes:36,nodeR:3.2,rungs:18,speed:.0005,repR:110,bgParts:60}; if(W<=1024) return{helixR:Math.min(W*.18,180),nodes:44,nodeR:3.5,rungs:22,speed:.00045,repR:130,bgParts:80}; return{helixR:Math.min(W*.15,220),nodes:52,nodeR:4,rungs:26,speed:.0004,repR:160,bgParts:100}; } // Background ambient particles let bgPs=[]; function initBg(n){ bgPs=[]; for(let i=0;i-900){const dx=p.x-mx,dy=p.y-my,d=Math.sqrt(dx*dx+dy*dy); if(d0){const f=(1-d/s.repR)*.6;p.vx+=dx/d*f;p.vy+=dy/d*f;}} p.x+=p.vx; p.y+=p.vy; p.vx*=.985; p.vy*=.985; if(p.x<0)p.x=W; if(p.x>W)p.x=0; if(p.y<0)p.y=H; if(p.y>H)p.y=0; ctx.globalAlpha=p.a; ctx.fillStyle='rgba('+p.col+',1)'; ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill(); } // ── DNA Double Helix — centered, vertical, rotating ── const cx=W/2, cy=H/2; const helixH=H*.75; // helix spans 75% of viewport height const steps=s.nodes; const R=s.helixR; const nr=s.nodeR; // Build helix points const strand1=[],strand2=[]; for(let i=0;i-900){ const d1=Math.sqrt((x1-mx)**2+(y-my)**2); if(d10){const f=(1-d1/s.repR)*18;dx1=(x1-mx)/d1*f;dy1=(y-my)/d1*f;} const d2=Math.sqrt((x2-mx)**2+(y-my)**2); if(d20){const f=(1-d2/s.repR)*18;dx2=(x2-mx)/d2*f;dy2=(y-my)/d2*f;} } strand1.push({x:x1+dx1,y:y+dy1,z:z1}); strand2.push({x:x2+dx2,y:y+dy2,z:z2}); } // Draw rungs (base pairs) — behind strands ctx.globalAlpha=1; for(let i=0;i { toast('Welcome to Geneius! Select your DNA file to begin analysis.'); // Auto-open file picker const fileInput = document.getElementById('file-input'); if (fileInput) fileInput.click(); }, 800); } })();