Spaces:
Build error
Build error
Fetching metadata from the HF Docker repository...
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GFP Mutation Explorer</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.9/babel.min.js"></script> | |
| <style> | |
| body { margin: 0; padding: 0; } | |
| * { -webkit-font-smoothing: antialiased; } | |
| ::-webkit-scrollbar { width: 5px; height: 5px; } | |
| ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| var useState = React.useState, useCallback = React.useCallback, useEffect = React.useEffect, useRef = React.useRef; | |
| var SEQ = "SKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKFICTTGKLPVPWPTLVTTLSYGVQCFSRYPDHMKQHDFFKSAMPEGYVQERTIFFKDDGNYKTRAEVKFEGDTLVNRIELKGIDFKEDGNILGHKLEYNYNSHNVYIMADKQKNGIKVNFKIRHNIEDGSVQLADHYQQNTPIGDGPVLLPDNHYLSTQSALSKDPNEKRDHMVLLEFVTAAGITHGMDELYK"; | |
| var AAs = ["A","C","D","E","F","G","H","I","K","L","M","N","P","Q","R","S","T","V","W","Y"]; | |
| var AA3 = {A:"Ala",C:"Cys",D:"Asp",E:"Glu",F:"Phe",G:"Gly",H:"His",I:"Ile",K:"Lys",L:"Leu",M:"Met",N:"Asn",P:"Pro",Q:"Gln",R:"Arg",S:"Ser",T:"Thr",V:"Val",W:"Trp",Y:"Tyr"}; | |
| var AANAME = {A:"Alanine",C:"Cysteine",D:"Aspartate",E:"Glutamate",F:"Phenylalanine",G:"Glycine",H:"Histidine",I:"Isoleucine",K:"Lysine",L:"Leucine",M:"Methionine",N:"Asparagine",P:"Proline",Q:"Glutamine",R:"Arginine",S:"Serine",T:"Threonine",V:"Valine",W:"Tryptophan",Y:"Tyrosine"}; | |
| var CHROMO = new Set([63,64,65]); | |
| var CA_Z=[-16.0,-18.7,-17.3,-15.6,-18.4,-17.4,-14.4,-15.3,-16.0,-14.3,-14.3,-13.3,-10.6,-10.1,-6.8,-6.3,-2.8,-2.2,1.3,4.6,8.3,11.0,10.4,7.7,3.9,2.2,-1.1,-1.7,-5.0,-5.2,-7.8,-8.1,-9.7,-9.1,-10.4,-9.7,-10.7,-7.4,-5.4,-5.1,-4.6,-4.3,-3.9,-2.9,-1.6,-1.6,0.0,-2.3,0.4,3.1,6.3,6.3,9.0,9.4,10.3,7.3,9.0,8.7,5.4,3.9,4.8,2.1,-0.3,-0.5,-0.0,-2.9,-5.5,-4.5,-8.2,-7.8,-5.3,-6.1,-6.2,-6.6,-9.9,-8.3,-7.2,-10.6,-10.6,-7.6,-9.5,-9.3,-9.0,-12.3,-14.3,-13.5,-15.5,-17.7,-17.2,-13.5,-10.2,-7.4,-3.8,-1.1,2.4,6.0,8.7,12.4,15.0,18.5,20.8,17.8,14.6,11.3,7.7,4.8,1.3,-2.3,-4.8,-8.5,-10.0,-13.8,-16.3,-18.5,-21.2,-20.0,-16.3,-13.2,-11.7,-8.2,-7.7,-4.2,-2.4,1.4,4.2,8.0,10.5,14.3,14.9,18.1,18.8,21.8,19.9,18.4,14.8,14.8,17.7,15.8,18.5,16.0,15.3,11.7,12.0,10.1,11.3,9.4,8.4,7.0,5.0,4.4,1.1,0.3,-2.8,-2.5,-5.5,-5.1,-5.6,-8.3,-6.2,-3.1,0.7,2.5,6.3,8.5,11.7,11.4,14.1,14.3,16.6,19.1,20.1,23.8,23.1,23.0,19.8,18.0,16.4,13.7,11.9,8.1,6.0,2.2,-0.7,-4.1,-7.3,-11.0,-12.3,-15.9,-18.1,-18.5,-16.4,-13.4,-11.4,-8.0,-5.4,-2.7,0.9,0.9,2.4,1.5,3.5,3.6,4.8,4.8,5.8,5.9,5.1,4.0,1.0,2.4,-0.5,-0.3,1.6,1.3,3.8,2.1,2.8,1.1,1.0,0.6,0.3,-0.5,-0.9,-1.5,-1.5,0.5,0.9,3.9,5.1,2.2,-0.2,1.0,1.5,-2.2,-3.1,-1.8,-2.0]; | |
| var ssArr = new Array(SEQ.length).fill("C"); | |
| [[3,6],[55,62],[67,69],[74,79],[81,84],[154,156]].forEach(function(p){for(var i=p[0];i<=p[1]&&i<SEQ.length;i++)ssArr[i]="H";}); | |
| [[9,20],[23,34],[39,46],[90,98],[103,113],[116,126],[146,153],[158,168],[174,185],[197,206],[215,225]].forEach(function(p){for(var i=p[0];i<=p[1]&&i<SEQ.length;i++)ssArr[i]="E";}); | |
| var HYDRO=new Set(["A","V","I","L","M","F","W","P"]),POLAR=new Set(["S","T","Y","H","C","N","Q"]),POSC=new Set(["K","R"]),NEGC=new Set(["D","E"]); | |
| function sameGrp(a,b){return(HYDRO.has(a)&&HYDRO.has(b))||(POLAR.has(a)&&POLAR.has(b))||(POSC.has(a)&&POSC.has(b))||(NEGC.has(a)&&NEGC.has(b));} | |
| function hash(a,b){var h=((a*2654435761)^(b*2246822519))>>>0;h=((h>>16)^h)*0x45d9f3b;h=((h>>16)^h)*0x45d9f3b;return((h>>16)^h)&0xffff;} | |
| // ββ Real scores from METL model (injected by backend) ββ | |
| var oScores = /*__ORACLE_SCORES__*/[]; | |
| var dScores = /*__DESIGN_SCORES__*/[]; | |
| var allM=[];for(var p2=0;p2<SEQ.length;p2++)for(var a2=0;a2<20;a2++){var sc=oScores[p2][a2];if(sc!==null)allM.push({pos:p2,aaIdx:a2,score:sc});} | |
| allM.sort(function(a,b){return b.score-a.score;}); | |
| var trainData=[],trainSet=new Set(),sampPos=new Set(); | |
| for(var i0=0;i0<allM.length&&trainData.length<8;i0++){var mm=allM[i0];if(!sampPos.has(mm.pos)){trainData.push(mm);sampPos.add(mm.pos);trainSet.add(mm.pos+"-"+mm.aaIdx);}} | |
| var botM=allM.slice().reverse();for(var i1=0;i1<botM.length&&trainData.length<18;i1++){var m2=botM[i1];if(!sampPos.has(m2.pos)){trainData.push(m2);sampPos.add(m2.pos);trainSet.add(m2.pos+"-"+m2.aaIdx);}} | |
| for(var p3=0;p3<SEQ.length;p3+=4){if(trainData.length>=64)break;if(!sampPos.has(p3)){var a3=hash(p3,999)%20;var s3=oScores[p3][a3];if(s3!==null){trainData.push({pos:p3,aaIdx:a3,score:s3});sampPos.add(p3);trainSet.add(p3+"-"+a3);}}} | |
| trainData.sort(function(a,b){return a.pos-b.pos;}); | |
| var dRnk=[];for(var p4=0;p4<SEQ.length;p4++)for(var a4=0;a4<20;a4++){var ds=dScores[p4][a4];if(ds!==null&&!trainSet.has(p4+"-"+a4))dRnk.push({pos:p4,aaIdx:a4,ds:ds,os:oScores[p4][a4]});} | |
| dRnk.sort(function(a,b){return b.ds-a.ds;}); | |
| var aiPicks=dRnk.slice(0,5).map(function(m){return{pos:m.pos,aaIdx:m.aaIdx,score:m.os};}); | |
| var CA2D=[[97.4,144],[110.4,143.5],[125.6,135.2],[115.4,122.3],[119.6,110.4],[137.3,109.6],[134.1,99.2],[119.3,89],[129.4,74],[144.7,68.6],[158.1,81],[175.7,77.3],[181.8,88.9],[198.7,95.7],[202.8,104.4],[219,112.7],[222.3,119.4],[238,128.5],[240.2,135.1],[247.8,138.3],[244.3,137.5],[253.1,146.8],[270,140],[267.3,126.8],[265.5,125],[254.4,112.7],[246.5,107.9],[231.7,97.3],[225.7,90.7],[208.5,84.5],[201.1,73.8],[184,67.8],[167.3,69.4],[151.4,78.2],[134,78.1],[122.5,92.4],[107.8,82.3],[109.7,73],[120,85],[133,72],[150.9,75.8],[165.3,64.3],[182.6,70.7],[198.6,62.8],[210.7,75],[228.7,71.2],[242.4,80.6],[256.8,81.6],[266.8,89.9],[265.2,77],[255.6,78],[242.9,91.2],[246.5,104.6],[228.4,107.4],[215.7,94.4],[205.8,88.6],[189.6,93.7],[194.3,111.4],[203.2,110.2],[187.6,103.1],[179,119],[189.4,126.8],[188.8,112.5],[171.4,106],[161.2,121.1],[170.7,127.6],[162.2,116.5],[144.9,121.2],[141,122],[136.9,104.1],[123.1,107.2],[106.3,100.5],[93,113.1],[74.9,109.1],[68,114.3],[59,128.3],[75.3,135.5],[84.4,134.6],[84.8,153.1],[96.2,152],[109.5,142.8],[122.6,156.1],[135.8,143.7],[130.8,136.1],[125.6,151.3],[142.3,158.5],[149.5,144.3],[158.8,140.4],[166.9,156.9],[166.8,154],[165.3,162],[176.5,167.7],[174.8,163.9],[182.1,174.8],[182.5,166.8],[186.8,168.8],[184.1,156.3],[185.5,161.4],[188.2,148.7],[182.9,152.4],[197.7,153.2],[208.4,148.3],[214.9,156.2],[207.3,161.1],[208.1,154.1],[207.8,165.7],[205.8,159.8],[200.7,163],[194.3,150.7],[191.7,151.1],[179.5,139.6],[179.6,137.7],[173.4,125.3],[187.5,120.2],[187.7,107.5],[175.2,95.2],[178.4,97.4],[176.3,108.2],[191.1,115.9],[192.2,123.6],[208.4,132.7],[211.2,138.7],[223.1,150],[223,147.3],[232,155.8],[229,153.3],[232.8,166.1],[234.4,166.7],[233.5,148.5],[241,141.5],[249.1,125.3],[238.1,122],[222.7,126],[211.9,113.4],[217.2,116.3],[235.3,118.4],[236.5,106.5],[227.3,93.2],[214.5,90.4],[200.4,88.7],[191.4,73.1],[189.7,67.6],[171.4,67.8],[162.6,81.1],[146.2,87.9],[130.8,91.8],[128.2,109.6],[116.5,122.1],[124.5,135.8],[115.5,151.4],[115.8,159.7],[110.2,176.9],[111.9,187.2],[121.7,202.5],[116.1,212.2],[128,226.1],[142.2,214.6],[133.5,205.1],[135.1,189.9],[132.1,179.9],[134.8,181.5],[137.1,165.4],[136.3,163.7],[137.4,148.9],[144.1,141.9],[154,126.8],[155.9,113.9],[171.7,104.4],[172.1,89.8],[186,89.1],[192,72.3],[188.7,75.7],[170.5,77.8],[170.3,96.3],[166.7,105.8],[176.5,119.1],[165.7,131.5],[169.8,143.3],[161.6,157.2],[162.6,155.1],[160.3,169.9],[160.6,169.4],[158.3,181.3],[154.7,174.2],[155.2,184.1],[156.5,179.3],[173.7,178.8],[167.4,180.7],[159,192.9],[141.9,186.2],[126.4,188],[124.2,176.8],[109.2,172],[107.5,180.4],[97.6,170.1],[85.5,173.7],[89,168.4],[91.3,150.2],[102.5,137.8],[111,122.4],[109.2,107.3],[125.5,99],[130,82.3],[147.2,76.3],[158.2,62.5],[176.4,60.5],[184.5,44.4],[200.2,36.5],[206.3,26.5],[214,10.7],[225.5,10],[232.4,27.2],[248,30.3],[247.9,48.7],[239.7,60.2],[223.3,61.4],[205.4,62.8],[190.7,55.8],[176.4,67.2],[159.1,61.7],[146,74.9],[128.4,78.1],[121.8,95.1],[104.5,100.9],[95.8,117.1],[81.3,122.9],[73.3,139.4],[71,150.6],[54.8,157],[46.2,164.4],[57.7,155.7],[50.8,139.7],[33.4,145.3],[32.9,150],[35.4,132.2],[18.5,129.1],[10,145.5]]; | |
| function sColor(s){if(s===null)return"transparent";if(s<-2.5)return"#7f1d1d";if(s<-1.5)return"#b91c1c";if(s<-0.8)return"#ef4444";if(s<-0.3)return"#fca5a5";if(s<0.2)return"#fef3f2";if(s<0.7)return"#dbeafe";if(s<1.2)return"#60a5fa";if(s<1.8)return"#2563eb";return"#1e3a8a";} | |
| function sLabel(s){if(s>1)return"Highly beneficial";if(s>0.3)return"Mildly beneficial";if(s>-0.3)return"Near neutral";if(s>-1.5)return"Deleterious";return"Highly deleterious";} | |
| function ssClr(i){if(CHROMO.has(i))return"#22c55e";if(ssArr[i]==="E")return"#3b82f6";if(ssArr[i]==="H")return"#a855f7";return"#94a3b8";} | |
| var CELL=14,PP=50; | |
| function StructView(props){ | |
| var hl=props.hl,onH=props.onH,selP=props.selP||[];var sS=new Set(selP); | |
| function spline(pts,n){if(pts.length<2)return pts;var r=[];for(var i=0;i<pts.length-1;i++){var p0=pts[Math.max(0,i-1)],p1=pts[i],p2=pts[i+1],p3=pts[Math.min(pts.length-1,i+2)];for(var t=0;t<n;t++){var u=t/n,u2=u*u,u3=u2*u;r.push([.5*((2*p1[0])+(-p0[0]+p2[0])*u+(2*p0[0]-5*p1[0]+4*p2[0]-p3[0])*u2+(-p0[0]+3*p1[0]-3*p2[0]+p3[0])*u3),.5*((2*p1[1])+(-p0[1]+p2[1])*u+(2*p0[1]-5*p1[1]+4*p2[1]-p3[1])*u2+(-p0[1]+3*p1[1]-3*p2[1]+p3[1])*u3)])}}r.push(pts[pts.length-1]);return r} | |
| var segs=[],si=0;while(si<SEQ.length){var ss=ssArr[si],sj=si;while(sj<SEQ.length&&ssArr[sj]===ss)sj++;segs.push({t:ss,s:si,e:sj-1});si=sj} | |
| var els=[]; | |
| segs.forEach(function(seg,idx){ | |
| var raw=[];for(var k=seg.s;k<=seg.e&&k<CA2D.length;k++)raw.push(CA2D[k]);if(raw.length<2)return; | |
| var sm=spline(raw,5);var d="M"+sm.map(function(p){return p[0].toFixed(1)+","+p[1].toFixed(1)}).join("L"); | |
| if(seg.t==="E"){ | |
| els.push(<path key={"eo"+idx} d={d} fill="none" stroke="#166534" strokeWidth={6} opacity={0.7} strokeLinecap="round" strokeLinejoin="round"/>); | |
| els.push(<path key={"ei"+idx} d={d} fill="none" stroke="#4ade80" strokeWidth={3} opacity={0.85} strokeLinecap="round" strokeLinejoin="round"/>); | |
| var L=sm[sm.length-1],P=sm[Math.max(0,sm.length-4)];var dx=L[0]-P[0],dy=L[1]-P[1],dl=Math.sqrt(dx*dx+dy*dy);if(dl>0){dx/=dl;dy/=dl} | |
| var tp=[L[0]+dx*3,L[1]+dy*3];els.push(<polygon key={"ea"+idx} points={(L[0]-dy*4.5)+","+(L[1]+dx*4.5)+" "+tp[0]+","+tp[1]+" "+(L[0]+dy*4.5)+","+(L[1]-dx*4.5)} fill="#4ade80" opacity={0.85}/>); | |
| }else if(seg.t==="H"){ | |
| els.push(<path key={"ho"+idx} d={d} fill="none" stroke="#155e75" strokeWidth={5} opacity={0.7} strokeLinecap="round" strokeLinejoin="round"/>); | |
| els.push(<path key={"hi"+idx} d={d} fill="none" stroke="#22d3ee" strokeWidth={2.5} opacity={0.85} strokeLinecap="round" strokeLinejoin="round"/>); | |
| }else{ | |
| els.push(<path key={"c"+idx} d={d} fill="none" stroke="#4ade80" strokeWidth={1} opacity={0.2} strokeLinecap="round"/>)} | |
| }); | |
| var dots=[]; | |
| for(var j=0;j<CA2D.length;j++){var isHL=hl===j,isSel=sS.has(j),isChr=CHROMO.has(j); | |
| if(isHL||isSel||isChr){dots.push(<circle key={"d"+j} cx={CA2D[j][0]} cy={CA2D[j][1]} r={isHL?5:isSel?4:3} fill={isHL?"#ec4899":isSel?"#f97316":"#22c55e"} stroke="white" strokeWidth={1}/>)} | |
| dots.push(<circle key={"h"+j} cx={CA2D[j][0]} cy={CA2D[j][1]} r={5} fill="transparent" style={{cursor:"pointer"}} onMouseEnter={function(idx){return function(){onH(idx)}}(j)} onMouseLeave={function(){onH(null)}}/>)} | |
| return( | |
| <div style={{borderRadius:8,overflow:"hidden",border:"1px solid #1e293b",background:"#0f172a",padding:8,textAlign:"center"}}> | |
| <div style={{fontSize:10,fontWeight:700,color:"#94a3b8",marginBottom:2}}>GFP Structure</div> | |
| <svg viewBox="-5 -5 300 256" style={{width:"100%",maxWidth:260}}>{els}{dots}</svg> | |
| <div style={{display:"flex",justifyContent:"center",gap:10,fontSize:8,marginTop:3}}> | |
| <span style={{color:"#4ade80"}}>β strand</span> | |
| <span style={{color:"#22d3ee"}}>β helix</span> | |
| <span style={{color:"#22c55e"}}>β chr</span> | |
| </div> | |
| <div style={{fontSize:10,fontWeight:600,color:"#ec4899",marginTop:2,height:16,overflow:"hidden"}}>{hl!==null&&hl>=0&&hl<SEQ.length?("Pos "+hl+": "+SEQ[hl]+" ("+AA3[SEQ[hl]]+")"):" "}</div> | |
| </div>); | |
| } | |
| function SeqStrip(props){ | |
| var hl=props.hl,onClick=props.onClick;var ref=useRef(null); | |
| useEffect(function(){if(hl!==null&&ref.current){var el=ref.current.querySelector("[data-p='"+hl+"']");if(el)el.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"});}},[hl]); | |
| return( | |
| <div ref={ref} style={{overflowX:"auto",padding:"4px 0",background:"#f8fafc",borderRadius:8,border:"1px solid #e2e8f0",marginBottom:8}}> | |
| <div style={{display:"flex",paddingLeft:4,minWidth:SEQ.length*14}}> | |
| {SEQ.split("").map(function(aa,i){var isH=hl===i;return <div key={i} data-p={i} onClick={function(){onClick(i);}} style={{width:14,height:18,display:"flex",alignItems:"center",justifyContent:"center",fontFamily:"monospace",fontSize:9,fontWeight:isH?800:400,color:isH?"white":CHROMO.has(i)?"#16a34a":"#475569",background:isH?"#2563eb":ssArr[i]==="E"?"#eff6ff":ssArr[i]==="H"?"#faf5ff":"transparent",borderRadius:isH?3:0,cursor:"pointer"}}>{aa}</div>;})} | |
| </div> | |
| <div style={{display:"flex",paddingLeft:4,minWidth:SEQ.length*14}}> | |
| {SEQ.split("").map(function(_,i){return <div key={i} style={{width:14,textAlign:"center",fontSize:6,color:i%10===0?"#475569":i%5===0?"#94a3b8":"transparent"}}>{i%5===0?String(i):""}</div>;})} | |
| </div> | |
| </div>); | |
| } | |
| function Grid(props){ | |
| var ps=props.ps,pe=props.pe,scores=props.scores,hover=props.hover,setHover=props.setHover,sel=props.sel,toggle=props.toggle,showAll=props.showAll!==false,showDots=props.showDots===true; | |
| var cols=pe-ps,rows=[]; | |
| for(var ai=0;ai<20;ai++){var cells=[];for(var c=0;c<cols;c++){(function(pos,aaIdx){ | |
| var s=scores[pos][aaIdx],isWT=SEQ[pos]===AAs[aaIdx],isSel=sel.some(function(m){return m.pos===pos&&m.aaIdx===aaIdx;}),isHov=hover&&hover.pos===pos&&hover.aaIdx===aaIdx,isTr=trainSet.has(pos+"-"+aaIdx); | |
| var bg=isWT?"transparent":(showAll||isTr)?sColor(s):"#f1f5f9"; | |
| var brd=isSel?"2px solid #f59e0b":isWT?"1px solid #94a3b8":(isHov&&!isWT)?"1px solid #2563eb":"0.5px solid rgba(0,0,0,0.05)"; | |
| cells.push(<div key={c} onMouseEnter={function(){setHover({pos:pos,aaIdx:aaIdx});}} onMouseLeave={function(){setHover(null);}} onClick={function(){if(!isWT&&s!==null)toggle(pos,aaIdx);}} style={{width:CELL,height:CELL,boxSizing:"border-box",background:bg,border:brd,cursor:isWT?"default":"pointer",borderRadius:isSel?2:0,transform:"none",zIndex:isHov?10:isSel?5:1,position:"relative",display:"flex",alignItems:"center",justifyContent:"center"}}> | |
| {isSel&&<span style={{fontSize:6,color:"#92400e"}}>{"β¦"}</span>} | |
| {showDots&&isTr&&!isSel&&<span style={{fontSize:4,color:"rgba(0,0,0,0.5)"}}>{"β"}</span>} | |
| </div>);})(ps+c,ai);}rows.push(<div key={ai} style={{display:"flex",height:CELL}}>{cells}</div>);} | |
| return( | |
| <div style={{overflowX:"auto",overflowY:"hidden",maxWidth:"100%"}}> | |
| <div style={{display:"inline-block"}}> | |
| <div style={{marginLeft:24,display:"flex",marginBottom:1}}>{Array.from({length:cols},function(_,c){var pos=ps+c;return <div key={c} style={{width:CELL,textAlign:"center",fontSize:6,fontWeight:700,color:CHROMO.has(pos)?"#16a34a":"#aaa",fontFamily:"monospace"}}>{SEQ[pos]}</div>;})}</div> | |
| <div style={{display:"flex"}}><div style={{width:24,flexShrink:0}}>{AAs.map(function(aa){return <div key={aa} style={{height:CELL,display:"flex",alignItems:"center",justifyContent:"flex-end",paddingRight:3,fontSize:8,fontWeight:700,fontFamily:"monospace",color:"#374151"}}>{aa}</div>;})}</div><div>{rows}</div></div> | |
| <div style={{marginLeft:24,display:"flex"}}>{Array.from({length:cols},function(_,c){var pos=ps+c;return <div key={c} style={{width:CELL,textAlign:"center",fontSize:6,color:pos%10===0?"#333":pos%5===0?"#999":"transparent",fontWeight:pos%10===0?700:400,marginTop:1}}>{pos%5===0?String(pos):""}</div>;})}</div> | |
| </div> | |
| </div>); | |
| } | |
| function PageNav(props){ | |
| var pg=props.pg,set=props.set,tot=props.tot;var btns=[]; | |
| for(var i=0;i<tot;i++){(function(idx){btns.push(<button key={idx} onClick={function(){set(idx);}} style={{padding:"3px 8px",borderRadius:4,border:pg===idx?"2px solid #2563eb":"1px solid #ddd",background:pg===idx?"#eff6ff":"white",fontWeight:pg===idx?700:400,cursor:"pointer",fontFamily:"inherit",fontSize:10}}>{idx*PP+"-"+Math.min((idx+1)*PP-1,SEQ.length-1)}</button>);})(i);} | |
| return <div style={{display:"flex",gap:3,marginBottom:6,flexWrap:"wrap"}}>{btns}</div>; | |
| } | |
| function Tip(props){ | |
| var h=props.h,scores=props.scores,extra=props.extra;var pos=h?h.pos:0,ai=h?h.aaIdx:0,isWT=h?(SEQ[pos]===AAs[ai]):true,s=h?scores[pos][ai]:0; | |
| return( | |
| <div style={{background:"white",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",boxShadow:"0 2px 10px rgba(0,0,0,0.1)",fontSize:11,height:30,overflow:"hidden",opacity:h?1:0,transition:"opacity 0.1s"}}> | |
| <b>{"Pos "+pos}</b>{h&&CHROMO.has(pos)?" π’":""} | |
| {" Β· "+(ssArr[pos]==="E"?"Ξ²":ssArr[pos]==="H"?"Ξ±":"coil")+" Β· "} | |
| {isWT?<span>{SEQ[pos]+" (wild type)"}</span>:( | |
| <span><span style={{color:"#dc2626"}}>{SEQ[pos]}</span>{"β"}<span style={{color:"#2563eb",fontWeight:700}}>{AAs[ai]}</span>{" = "}<b style={{color:s>0?"#2563eb":"#dc2626"}}>{s!==null?s.toFixed(2):"?"}</b>{" "+sLabel(s)}</span> | |
| )} | |
| {extra&&<span style={{color:extra.color,marginLeft:4}}>{extra.text}</span>} | |
| </div>); | |
| } | |
| function Legend(){return( | |
| <div style={{display:"flex",gap:8,fontSize:9,color:"#64748b",flexWrap:"wrap",marginTop:6,marginBottom:6}}> | |
| <span><span style={{display:"inline-block",width:30,height:6,borderRadius:2,background:"linear-gradient(90deg,#b91c1c,#fef3f2,#2563eb)",verticalAlign:"middle"}}/> DelβBen</span> | |
| <span><span style={{display:"inline-block",width:8,height:8,border:"1px solid #94a3b8",verticalAlign:"middle"}}/> WT</span> | |
| <span><span style={{display:"inline-block",width:8,height:8,border:"2px solid #f59e0b",borderRadius:2,verticalAlign:"middle"}}/> Sel</span> | |
| <span><span style={{display:"inline-block",width:8,height:3,background:"#22c55e",borderRadius:1,verticalAlign:"middle"}}/> Chrom</span> | |
| </div>);} | |
| function App(){ | |
| var s1=useState("learn"),tab=s1[0],setTab=s1[1]; | |
| var s2=useState(0),pg=s2[0],setPg=s2[1]; | |
| var s3=useState(null),hov=s3[0],setHov=s3[1]; | |
| var s4=useState([]),sel=s4[0],setSel=s4[1]; | |
| var s5=useState(false),pred=s5[0],setPred=s5[1]; | |
| var s6=useState(false),pding=s6[0],setPding=s6[1]; | |
| var s7=useState([]),hPicks=s7[0],setHP=s7[1]; | |
| var s8=useState("learn"),cStep=s8[0],setCS=s8[1]; | |
| var s9=useState(-1),revI=s9[0],setRevI=s9[1]; | |
| var s10=useState(null),sHL=s10[0],setSHL=s10[1]; | |
| var s11=useState(false),aiRev=s11[0],setAiRev=s11[1]; | |
| var totP=Math.ceil(SEQ.length/PP),ps=pg*PP,pe=Math.min(ps+PP,SEQ.length); | |
| useEffect(function(){setSHL(hov?hov.pos:null);},[hov]); | |
| useEffect(function(){if(aiRev&&revI>=0&&revI<aiPicks.length){var t=setTimeout(function(){setRevI(function(v){return v+1;});},700);return function(){clearTimeout(t);};}},[aiRev,revI]); | |
| var toggle=useCallback(function(pos,ai){ | |
| var sL=tab==="compete"?setHP:setSel;var mx=tab==="compete"?5:10;var k=pos+"-"+ai; | |
| sL(function(prev){if(prev.find(function(m){return m.pos+"-"+m.aaIdx===k;}))return prev.filter(function(m){return m.pos+"-"+m.aaIdx!==k;});if(prev.length>=mx)return prev;return prev.concat([{pos:pos,aaIdx:ai,score:oScores[pos][ai]}]);}); | |
| if(tab!=="compete")setPred(false);},[tab]); | |
| var doPredict=useCallback(function(){setPding(true);setTimeout(function(){setPding(false);setPred(true);},1200);},[]); | |
| var hScore=hPicks.reduce(function(s,m){return s+m.score;},0); | |
| var aiSc=aiPicks.reduce(function(s,m){return s+m.score;},0); | |
| function resetC(){setCS("learn");setHP([]);setAiRev(false);setRevI(-1);} | |
| function getEx(){if(!hov)return null;var k=hov.pos+"-"+hov.aaIdx;if(tab==="compete"&&cStep==="pick"){return trainSet.has(k)?{text:"β training",color:"#16a34a"}:{text:"unknown",color:"#94a3b8"};}if(tab==="oracle"&&trainSet.has(k))return{text:"β train ex",color:"#16a34a"};return null;} | |
| function sClick(p){setSHL(p);setPg(Math.floor(p/PP));} | |
| function stHov(p){setSHL(p);if(p!==null)setPg(Math.floor(p/PP));} | |
| var stSel=(tab==="compete"?hPicks:sel).map(function(m){return m.pos;}); | |
| var curSel=tab==="compete"?hPicks:sel; | |
| return( | |
| <div style={{fontFamily:"Georgia,serif",background:"#faf9f7",minHeight:"100vh",color:"#1a1a1a",overflow:"hidden",maxWidth:"100%"}}> | |
| <div style={{background:"linear-gradient(135deg,#0f172a,#1e3a5f)",padding:"12px 14px 10px",color:"white"}}> | |
| <div style={{fontSize:16,fontWeight:700}}>GFP Mutation Explorer</div> | |
| <div style={{fontSize:10,opacity:0.6,marginBottom:8}}>Gitter Lab - Morgridge Institute</div> | |
| <div style={{display:"flex",gap:2,background:"rgba(255,255,255,0.1)",borderRadius:6,padding:2}}> | |
| {[["learn","π Learn"],["compete","π§ vs GFP-Design"],["oracle","π¬ Oracle"]].map(function(it){ | |
| return <button key={it[0]} onClick={function(){setTab(it[0]);setPg(0);setSel([]);setPred(false);if(it[0]!=="compete")resetC();}} style={{padding:"5px 10px",borderRadius:5,border:"none",cursor:"pointer",fontSize:10,fontWeight:600,fontFamily:"inherit",background:tab===it[0]?"white":"transparent",color:tab===it[0]?"#0f172a":"rgba(255,255,255,0.8)",flex:1}}>{it[1]}</button>;})} | |
| </div> | |
| </div> | |
| <div style={{padding:"10px 12px 24px",overflow:"hidden"}}> | |
| <Tip h={hov} scores={oScores} extra={getEx()}/> | |
| {tab==="learn"&&(<div> | |
| <p style={{fontSize:12,color:"#64748b",margin:"0 0 8px"}}>A <b>mutation</b> swaps one amino acid for another. Hover the sequence, click the grid to pick mutations.</p> | |
| <SeqStrip hl={sHL} onClick={sClick}/> | |
| <StructView hl={sHL} onH={stHov} selP={stSel}/> | |
| <div style={{marginTop:8,fontWeight:700,fontSize:13,marginBottom:4}}>Mutation Grid</div> | |
| <PageNav pg={pg} set={setPg} tot={totP}/><Legend/> | |
| <Grid ps={ps} pe={pe} scores={oScores} hover={hov} setHover={setHov} sel={sel} toggle={toggle} showAll={true}/> | |
| {sel.length>0&&(<div style={{marginTop:8,background:"#f8fafc",borderRadius:8,padding:10}}> | |
| <div style={{fontWeight:700,fontSize:12,marginBottom:4}}>{"Selected ("+sel.length+")"}</div> | |
| {sel.map(function(m,i){return <div key={i} style={{display:"flex",justifyContent:"space-between",fontSize:11,padding:"2px 0"}}><span style={{fontFamily:"monospace",fontWeight:700}}>{SEQ[m.pos]+m.pos+AAs[m.aaIdx]}</span><span style={{display:"flex",gap:4,alignItems:"center"}}>{pred&&<span style={{color:m.score>0?"#2563eb":"#dc2626",fontWeight:600}}>{(m.score>0?"+":"")+m.score.toFixed(2)}</span>}<button onClick={function(){setSel(function(prev){return prev.filter(function(_,j){return j!==i;});});setPred(false);}} style={{background:"none",border:"none",cursor:"pointer",color:"#999",fontSize:12}}>{"Γ"}</button></span></div>;})} | |
| {!pred&&<button onClick={doPredict} disabled={pding} style={{width:"100%",marginTop:6,padding:8,borderRadius:6,border:"none",background:pding?"#94a3b8":"#2563eb",color:"white",fontWeight:700,fontSize:12,cursor:"pointer",fontFamily:"inherit"}}>{pding?"Running...":"𧬠Predict"}</button>} | |
| {pred&&<div style={{marginTop:6,padding:6,background:"#f0fdf4",borderRadius:4,textAlign:"center",fontSize:11,fontWeight:700,color:"#16a34a"}}>{"Total: "+sel.reduce(function(s,m){return s+m.score;},0).toFixed(2)}</div>} | |
| </div>)} | |
| </div>)} | |
| {tab==="compete"&&(<div> | |
| <div style={{textAlign:"center",marginBottom:10}}> | |
| <div style={{fontSize:16,fontWeight:800}}>{"π§ You vs π€ GFP-Design"}</div> | |
| <div style={{fontSize:11,color:"#64748b"}}>Both see 64 examples. Oracle (70k) judges.</div> | |
| </div> | |
| <div style={{display:"flex",justifyContent:"center",gap:3,marginBottom:10}}> | |
| {[["learn","1. Study"],["pick","2. Pick"],["reveal","3. Result"]].map(function(it){return <div key={it[0]} style={{padding:"5px 12px",borderRadius:12,fontSize:10,fontWeight:600,background:cStep===it[0]?"#0f172a":"#f1f5f9",color:cStep===it[0]?"white":"#999"}}>{it[1]}</div>;})} | |
| </div> | |
| <SeqStrip hl={sHL} onClick={sClick}/> | |
| {cStep==="learn"&&(<div> | |
| <div style={{background:"white",borderRadius:8,border:"1px solid #e2e8f0",overflow:"hidden",marginBottom:10}}> | |
| <div style={{padding:"8px 10px",background:"#f8fafc",borderBottom:"1px solid #e2e8f0",fontWeight:700,fontSize:11}}>64 Training Examples</div> | |
| <div style={{maxHeight:280,overflowY:"auto"}}> | |
| <table style={{width:"100%",borderCollapse:"collapse",fontSize:10}}> | |
| <thead><tr style={{position:"sticky",top:0,background:"#f8fafc"}}>{["Pos","WT","β","Mut","SS","Score"].map(function(h){return <th key={h} style={{padding:"4px 6px",textAlign:"left",borderBottom:"1px solid #e2e8f0",fontSize:9,color:"#64748b"}}>{h}</th>;})}</tr></thead> | |
| <tbody>{trainData.slice(0,64).map(function(m,i){return( | |
| <tr key={i} style={{background:i%2===0?"white":"#fafbfc"}} onMouseEnter={function(){setSHL(m.pos);}} onMouseLeave={function(){setSHL(null);}}> | |
| <td style={{padding:"3px 6px",fontFamily:"monospace",fontWeight:700}}>{m.pos}</td> | |
| <td style={{padding:"3px 6px",fontFamily:"monospace"}}>{SEQ[m.pos]}</td> | |
| <td style={{padding:"3px 6px",color:"#ccc"}}>{"β"}</td> | |
| <td style={{padding:"3px 6px",fontFamily:"monospace",fontWeight:700,color:"#2563eb"}}>{AAs[m.aaIdx]}</td> | |
| <td style={{padding:"3px 6px",fontSize:9}}>{ssArr[m.pos]==="E"?"Ξ²":ssArr[m.pos]==="H"?"Ξ±":"β"}</td> | |
| <td style={{padding:"3px 6px",fontWeight:700,fontFamily:"monospace",color:m.score>0?"#2563eb":m.score>-1?"#d97706":"#dc2626"}}>{(m.score>0?"+":"")+m.score.toFixed(2)}</td> | |
| </tr>);})}</tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <button onClick={function(){setCS("pick");}} style={{width:"100%",padding:10,borderRadius:6,border:"none",background:"#0f172a",color:"white",fontWeight:700,fontSize:12,cursor:"pointer",fontFamily:"inherit"}}>{"Ready β Pick Mutations"}</button> | |
| </div>)} | |
| {cStep==="pick"&&(<div> | |
| <div style={{background:"#fefce8",border:"1px solid #fde68a",borderRadius:6,padding:"6px 10px",marginBottom:8,fontSize:11}}><b>Pick 5.</b>{" Colored=training. Gray=unknown. "}<b>{hPicks.length+"/5"}</b></div> | |
| <PageNav pg={pg} set={setPg} tot={totP}/><Legend/> | |
| <div style={{display:"flex",gap:12,alignItems:"flex-start",flexWrap:"wrap"}}> | |
| <div style={{flex:"1 1 auto"}}><Grid ps={ps} pe={pe} scores={oScores} hover={hov} setHover={setHov} sel={hPicks} toggle={toggle} showAll={false}/></div> | |
| <StructView hl={sHL} onH={stHov} selP={hPicks.map(function(m){return m.pos})}/> | |
| </div> | |
| {hPicks.length>0&&<div style={{marginTop:6,display:"flex",gap:4,flexWrap:"wrap"}}>{hPicks.map(function(m,i){return <span key={i} style={{padding:"3px 8px",background:"#fef3c7",borderRadius:4,fontFamily:"monospace",fontWeight:700,fontSize:10}}>{SEQ[m.pos]+m.pos+AAs[m.aaIdx]}<button onClick={function(){setHP(function(prev){return prev.filter(function(_,j){return j!==i;});});}} style={{background:"none",border:"none",cursor:"pointer",color:"#999",fontSize:10,marginLeft:2}}>{"Γ"}</button></span>;})}</div>} | |
| {hPicks.length===5&&<button onClick={function(){setCS("reveal");setAiRev(true);setRevI(0);}} style={{width:"100%",marginTop:10,padding:10,borderRadius:6,border:"none",background:"#dc2626",color:"white",fontWeight:700,fontSize:13,cursor:"pointer",fontFamily:"inherit"}}>{"π Lock In & Reveal"}</button>} | |
| </div>)} | |
| {cStep==="reveal"&&(<div> | |
| <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10,marginBottom:12}}> | |
| <div style={{background:"white",borderRadius:8,border:"2px solid #f59e0b",overflow:"hidden"}}> | |
| <div style={{background:"#fefce8",padding:"6px 10px",fontWeight:700,fontSize:12}}>{"π§ You"}</div> | |
| <div style={{padding:8}}>{hPicks.map(function(m,i){return <div key={i} style={{display:"flex",justifyContent:"space-between",padding:"3px 0",fontSize:11,fontFamily:"monospace"}}><b>{SEQ[m.pos]+m.pos+AAs[m.aaIdx]}</b><span style={{color:m.score>0?"#2563eb":"#dc2626",fontWeight:700}}>{(m.score>0?"+":"")+m.score.toFixed(2)}</span></div>;})}<div style={{marginTop:6,padding:6,background:"#fefce8",borderRadius:4,fontWeight:800,fontSize:14,textAlign:"center"}}>{"= "+hScore.toFixed(2)}</div></div> | |
| </div> | |
| <div style={{background:"white",borderRadius:8,border:"2px solid #2563eb",overflow:"hidden"}}> | |
| <div style={{background:"#eff6ff",padding:"6px 10px",fontWeight:700,fontSize:12}}>{"π€ AI"}</div> | |
| <div style={{padding:8}}>{aiPicks.map(function(m,i){var rv=i<revI;return <div key={i} style={{display:"flex",justifyContent:"space-between",padding:"3px 0",fontSize:11,fontFamily:"monospace",opacity:rv?1:0.3,transition:"opacity 0.5s"}}><b>{rv?SEQ[m.pos]+m.pos+AAs[m.aaIdx]:"???"}</b><span style={{color:"#2563eb",fontWeight:700}}>{rv?(m.score>0?"+":"")+m.score.toFixed(2):"??"}</span></div>;})}<div style={{marginTop:6,padding:6,background:"#eff6ff",borderRadius:4,fontWeight:800,fontSize:14,textAlign:"center"}}>{"= "+(revI>=5?aiSc.toFixed(2):aiPicks.slice(0,Math.max(0,revI)).reduce(function(s,m){return s+m.score;},0).toFixed(2))}</div></div> | |
| </div> | |
| </div> | |
| {revI>=5&&(<div style={{textAlign:"center",padding:14,background:aiSc>hScore?"#eff6ff":"#fefce8",borderRadius:10,border:"2px solid "+(aiSc>hScore?"#2563eb":"#f59e0b")}}> | |
| <div style={{fontSize:24}}>{aiSc>hScore?"π€":"π"}</div> | |
| <div style={{fontSize:16,fontWeight:800}}>{aiSc>hScore?"GFP-Design wins!":"You win!"}</div> | |
| <div style={{fontSize:11,color:"#64748b",margin:"4px 0"}}>{aiSc>hScore?"AI: "+(aiSc-hScore).toFixed(2)+" pts ahead. ML finds patterns from 64 examples.":"Your scientific intuition beat the model!"}</div> | |
| <button onClick={resetC} style={{marginTop:8,padding:"6px 20px",borderRadius:6,border:"none",background:"#0f172a",color:"white",fontWeight:700,fontSize:11,cursor:"pointer",fontFamily:"inherit"}}>Try Again</button> | |
| </div>)} | |
| </div>)} | |
| </div>)} | |
| {tab==="oracle"&&(<div> | |
| <div style={{fontWeight:800,fontSize:14,marginBottom:2}}>Oracle Landscape</div> | |
| <p style={{fontSize:11,color:"#64748b",margin:"0 0 8px"}}>{"β = training example (64 / ~4500)"}</p> | |
| <SeqStrip hl={sHL} onClick={sClick}/> | |
| <StructView hl={sHL} onH={stHov} selP={stSel}/> | |
| <PageNav pg={pg} set={setPg} tot={totP}/><Legend/> | |
| <Grid ps={ps} pe={pe} scores={oScores} hover={hov} setHover={setHov} sel={sel} toggle={toggle} showAll={true} showDots={true}/> | |
| </div>)} | |
| </div> | |
| </div>); | |
| } | |
| ReactDOM.render(React.createElement(App), document.getElementById("root")); | |
| </script> | |
| </body> | |
| </html> |