1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+ < head >
4+ < meta charset ="UTF-8 ">
5+ < title > Advanced Cre-Lox Simulator</ title >
6+ < style >
7+ : root {
8+ --loxp : # 0072B2 ; --l22 : # E69F00 ; --l511 : # 56B4E9 ; --ln : # F0E442 ;
9+ --orf : # CC79A7 ; --pA : # 000000 ;
10+ }
11+ body { font-family : 'Segoe UI' , sans-serif; background : # f0f2f5 ; padding : 20px ; }
12+ .container { max-width : 1100px ; margin : auto; background : white; padding : 25px ; border-radius : 12px ; box-shadow : 0 4px 20px rgba (0 , 0 , 0 , 0.1 ); }
13+
14+ .piece-wrapper {
15+ position : relative; padding : 10px 5px ; cursor : grab; display : flex; align-items : center;
16+ }
17+ .piece-wrapper : active { cursor : grabbing; }
18+
19+ .dna-block {
20+ height : 38px ; min-width : 85px ; padding : 0 12px ; color : white;
21+ font-weight : bold; display : flex; align-items : center; justify-content : center;
22+ font-size : 11px ; user-select : none;
23+ clip-path : polygon (0% 0% , 85% 0% , 100% 50% , 85% 100% , 0% 100% );
24+ }
25+ .piece-wrapper .rev .dna-block { clip-path : polygon (15% 0% , 100% 0% , 100% 100% , 15% 100% , 0% 50% ); }
26+
27+ .l22 , .ln { color : # 000 !important ; }
28+ .p { background : var (--loxp ); } .l22 { background : var (--l22 ); }
29+ .l511 { background : var (--l511 ); } .ln { background : var (--ln ); }
30+ .orf { background : var (--orf ); } .pa { background : var (--pA ); }
31+
32+ .toolbox , .strand {
33+ display : flex; align-items : center; padding : 15px ; border-radius : 8px ; margin-bottom : 20px ;
34+ }
35+ .toolbox { background : # eee ; gap : 5px ; flex-wrap : wrap; }
36+ .strand { min-height : 100px ; border : 2px dashed # ccc ; gap : 2px ; background : # fff ; overflow-x : auto; }
37+ .strand .drag-over { background : # e3f2fd ; border-color : var (--loxp ); }
38+
39+ .delete-btn {
40+ position : absolute; top : 0 ; right : 0 ; color : black; font-size : 16px ;
41+ cursor : pointer; font-weight : bold; display : none; z-index : 20 ; text-shadow : 0 0 2px white;
42+ }
43+ .piece-wrapper : hover .delete-btn { display : block; }
44+
45+ .share-box { display : flex; gap : 10px ; margin-bottom : 20px ; background : # f9f9f9 ; padding : 15px ; border-radius : 8px ; }
46+ input # sequence-string { flex-grow : 1 ; padding : 8px ; border : 1px solid # ccc ; border-radius : 4px ; font-family : monospace; }
47+
48+ .summary-table { width : 100% ; border-collapse : collapse; margin : 20px 0 ; border : 1px solid # ddd ; }
49+ .summary-table th , .summary-table td { padding : 10px ; border : 1px solid # ddd ; text-align : left; }
50+ .summary-table th { background : # f4f4f4 ; }
51+
52+ .outcome-box { border : 1px solid # ddd ; padding : 10px ; margin-bottom : 8px ; display : flex; align-items : center; gap : 10px ; border-radius : 6px ; font-size : 11px ; }
53+ .express-tag { padding : 2px 8px ; border-radius : 4px ; background : # 333 ; color : # fff ; font-weight : bold; min-width : 90px ; text-align : center; }
54+
55+ button { cursor : pointer; padding : 10px 20px ; border-radius : 6px ; border : none; font-weight : bold; }
56+ .btn-primary { background : # 2c3e50 ; color : # fff ; }
57+ </ style >
58+ </ head >
59+ < body >
60+
61+ < div class ="container ">
62+ < h2 > 🧬 Dynamic Cre-Lox Simulator</ h2 >
63+
64+ < div class ="share-box ">
65+ < input type ="text " id ="sequence-string " placeholder ="Construct code (e.g. P1,A1,P1) ">
66+ < button onclick ="loadFromCode() "> Load Code</ button >
67+ < button onclick ="updateCode() "> Refresh Code</ button >
68+ </ div >
69+
70+ < div class ="toolbox " id ="toolbox ">
71+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-source ="toolbox " data-id ="P "> < div class ="dna-block p "> LoxP</ div > </ div >
72+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-source ="toolbox " data-id ="L2 "> < div class ="dna-block l22 "> Lox2272</ div > </ div >
73+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-source ="toolbox " data-id ="L5 "> < div class ="dna-block l511 "> Lox511</ div > </ div >
74+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-source ="toolbox " data-id ="LN "> < div class ="dna-block ln "> LoxN</ div > </ div >
75+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-source ="toolbox " data-id ="PA "> < div class ="dna-block pa "> polyA</ div > </ div >
76+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-id ="A "> < div class ="dna-block orf "> ORF-A</ div > </ div >
77+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-id ="B "> < div class ="dna-block orf "> ORF-B</ div > </ div >
78+ < div class ="piece-wrapper " draggable ="true " ondragstart ="drag(event) " data-id ="C "> < div class ="dna-block orf "> ORF-C</ div > </ div >
79+ </ div >
80+
81+ < div id ="strand " class ="strand " ondragover ="allowDrop(event) " ondragleave ="dragLeave(event) " ondrop ="drop(event) "> </ div >
82+
83+ < div style ="display:flex; gap:10px; ">
84+ < button class ="btn-primary " onclick ="runSimulation() "> Simulate Outcomes</ button >
85+ < button onclick ="clearStrand() "> Reset Strand</ button >
86+ </ div >
87+
88+ < div id ="summary-section "> </ div >
89+ < div id ="results "> </ div >
90+ </ div >
91+
92+ < script >
93+ const strand = document . getElementById ( 'strand' ) ;
94+ let draggedData = null ;
95+
96+ const lookup = {
97+ "P" : { name : "LoxP" , type : "LOX" , css : "p" } , "L2" : { name : "Lox2272" , type : "LOX" , css : "l22" } ,
98+ "L5" : { name : "Lox511" , type : "LOX" , css : "l511" } , "LN" : { name : "LoxN" , type : "LOX" , css : "ln" } ,
99+ "PA" : { name : "polyA" , type : "PA" , css : "pa" } , "A" : { name : "ORF-A" , type : "ORF" , css : "orf" } ,
100+ "B" : { name : "ORF-B" , type : "ORF" , css : "orf" } , "C" : { name : "ORF-C" , type : "ORF" , css : "orf" }
101+ } ;
102+
103+ function allowDrop ( ev ) { ev . preventDefault ( ) ; strand . classList . add ( 'drag-over' ) ; }
104+ function dragLeave ( ev ) { strand . classList . remove ( 'drag-over' ) ; }
105+
106+ function drag ( ev ) {
107+ const isFromStrand = ev . currentTarget . parentElement . id === 'strand' ;
108+ draggedData = {
109+ id : ev . currentTarget . dataset . id ,
110+ dir : ev . currentTarget . dataset . dir || 1 ,
111+ index : isFromStrand ? Array . from ( strand . children ) . indexOf ( ev . currentTarget ) : - 1
112+ } ;
113+ }
114+
115+ function drop ( ev ) {
116+ ev . preventDefault ( ) ;
117+ strand . classList . remove ( 'drag-over' ) ;
118+ if ( draggedData . index !== - 1 ) {
119+ // Reordering internal
120+ const pieces = [ ...strand . querySelectorAll ( '.piece-wrapper' ) ] ;
121+ const target = pieces . find ( s => ev . clientX < s . getBoundingClientRect ( ) . left + s . getBoundingClientRect ( ) . width / 2 ) ;
122+ strand . insertBefore ( pieces [ draggedData . index ] , target || null ) ;
123+ } else {
124+ // New from toolbox
125+ addPiece ( draggedData . id , 1 , ev . clientX ) ;
126+ }
127+ updateCode ( ) ;
128+ }
129+
130+ function addPiece ( id , dir , mouseX ) {
131+ const info = lookup [ id ] ;
132+ const wrapper = document . createElement ( 'div' ) ;
133+ wrapper . className = `piece-wrapper placed ${ dir == - 1 ? 'rev' : '' } ` ;
134+ wrapper . dataset . id = id ; wrapper . dataset . dir = dir ;
135+ wrapper . draggable = true ;
136+ wrapper . ondragstart = drag ;
137+ wrapper . innerHTML = `<div class="dna-block ${ info . css } ">${ info . name } </div><div class="delete-btn" onclick="this.parentElement.remove(); updateCode();">×</div>` ;
138+ wrapper . onclick = ( e ) => { if ( e . target . className === 'delete-btn' ) return ; wrapper . dataset . dir = wrapper . dataset . dir == 1 ? - 1 : 1 ; wrapper . classList . toggle ( 'rev' ) ; updateCode ( ) ; } ;
139+ const siblings = [ ...strand . querySelectorAll ( '.piece-wrapper' ) ] ;
140+ const next = siblings . find ( s => mouseX < s . getBoundingClientRect ( ) . left + s . getBoundingClientRect ( ) . width / 2 ) ;
141+ strand . insertBefore ( wrapper , next || null ) ;
142+ updateCode ( ) ;
143+ }
144+
145+ function updateCode ( ) { document . getElementById ( 'sequence-string' ) . value = [ ...strand . querySelectorAll ( '.piece-wrapper' ) ] . map ( el => `${ el . dataset . id } ${ el . dataset . dir } ` ) . join ( ',' ) ; }
146+
147+ function loadFromCode ( ) {
148+ strand . innerHTML = '' ;
149+ const code = document . getElementById ( 'sequence-string' ) . value ;
150+ if ( ! code ) return ;
151+ code . split ( ',' ) . forEach ( part => {
152+ const match = part . match ( / ( [ A - Z 0 - 9 ] + ) ( - ? \d + ) / ) ;
153+ if ( match ) addPiece ( match [ 1 ] , parseInt ( match [ 2 ] ) , 99999 ) ;
154+ } ) ;
155+ }
156+
157+ function clearStrand ( ) { strand . innerHTML = '' ; document . getElementById ( 'sequence-string' ) . value = '' ; document . getElementById ( 'results' ) . innerHTML = '' ; document . getElementById ( 'summary-section' ) . innerHTML = '' ; }
158+
159+ function runSimulation ( ) {
160+ const initial = [ ...strand . querySelectorAll ( '.piece-wrapper' ) ] . map ( p => ( { ...lookup [ p . dataset . id ] , id : p . dataset . id , dir : parseInt ( p . dataset . dir ) } ) ) ;
161+ if ( ! initial . length ) return ;
162+
163+ let visited = new Map ( ) ;
164+ let queue = [ { seq : initial , steps : 0 } ] ;
165+ visited . set ( JSON . stringify ( initial ) , 0 ) ;
166+ let outcomes = [ ] ;
167+
168+ while ( queue . length > 0 && outcomes . length < 150 ) {
169+ let { seq, steps} = queue . shift ( ) ;
170+ let expr = "None" ;
171+ for ( let s of seq ) {
172+ if ( s . type === 'PA' && s . dir === 1 ) { expr = "Blocked" ; break ; }
173+ if ( s . type === 'ORF' && s . dir === 1 ) { expr = s . name ; break ; }
174+ }
175+ outcomes . push ( { seq, steps, expr} ) ;
176+
177+ let loxIdx = seq . map ( ( s , i ) => s . type === 'LOX' ? i : - 1 ) . filter ( i => i !== - 1 ) ;
178+ for ( let i = 0 ; i < loxIdx . length ; i ++ ) {
179+ for ( let j = i + 1 ; j < loxIdx . length ; j ++ ) {
180+ if ( seq [ loxIdx [ i ] ] . name !== seq [ loxIdx [ j ] ] . name ) continue ;
181+ let nextS = [ ] ;
182+ if ( seq [ loxIdx [ i ] ] . dir !== seq [ loxIdx [ j ] ] . dir ) {
183+ let mid = seq . slice ( loxIdx [ i ] + 1 , loxIdx [ j ] ) . reverse ( ) . map ( s => ( { ...s , dir : s . dir * - 1 } ) ) ;
184+ nextS = [ ...seq . slice ( 0 , loxIdx [ i ] + 1 ) , ...mid , ...seq . slice ( loxIdx [ j ] ) ] ;
185+ } else {
186+ // EXCISION: Fuse the two Lox sites into one
187+ nextS = [ ...seq . slice ( 0 , loxIdx [ i ] ) , seq [ loxIdx [ i ] ] , ...seq . slice ( loxIdx [ j ] + 1 ) ] ;
188+ }
189+ if ( ! visited . has ( JSON . stringify ( nextS ) ) ) { visited . set ( JSON . stringify ( nextS ) , steps + 1 ) ; queue . push ( { seq : nextS , steps : steps + 1 } ) ; }
190+ }
191+ }
192+ }
193+ render ( outcomes ) ;
194+ }
195+
196+ function render ( outcomes ) {
197+ const stats = { } ;
198+ outcomes . forEach ( o => {
199+ if ( ! stats [ o . expr ] ) stats [ o . expr ] = new Set ( ) ;
200+ stats [ o . expr ] . add ( o . steps ) ;
201+ } ) ;
202+
203+ let summaryHtml = `<h3>Phenotype Summary</h3><table class="summary-table"><thead><tr><th>Resulting Expression</th><th>Steps to Achieve (All Paths)</th></tr></thead><tbody>` ;
204+ for ( let key in stats ) {
205+ let stepsArr = Array . from ( stats [ key ] ) . sort ( ( a , b ) => a - b ) ;
206+ summaryHtml += `<tr><td><strong>${ key } </strong></td><td>${ stepsArr . join ( ', ' ) } </td></tr>` ;
207+ }
208+ summaryHtml += `</tbody></table>` ;
209+ document . getElementById ( 'summary-section' ) . innerHTML = summaryHtml ;
210+
211+ const res = document . getElementById ( 'results' ) ;
212+ res . innerHTML = `<h3>Possible DNA States (${ outcomes . length } )</h3>` ;
213+ outcomes . forEach ( o => {
214+ const div = document . createElement ( 'div' ) ;
215+ div . className = 'outcome-box' ;
216+ const dna = o . seq . map ( s => `<div class="dna-block ${ s . css } " style="transform: scale(0.65); min-width:70px; clip-path: polygon(${ s . dir == 1 ? '0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%' : '15% 0%, 100% 0%, 100% 100%, 15% 100%, 0% 50%' } )">${ s . id } </div>` ) . join ( '' ) ;
217+ div . innerHTML = `<div style="min-width:70px">Steps: ${ o . steps } </div> <div style="display:flex; flex-grow:1; overflow-x:auto;">${ dna } </div> <div class="express-tag">${ o . expr } </div>` ;
218+ res . appendChild ( div ) ;
219+ } ) ;
220+ }
221+ </ script >
222+ </ body >
223+ </ html >
0 commit comments