312312 .mix-btn {font-family : var (--ui );font-weight : 600 ;font-size : 14px ;padding : 9px 18px ;border : none;border-radius : 8px ;background : var (--accent );color : # fff ;cursor : pointer}
313313 .mix-btn : disabled {opacity : .5 }
314314 # mixResults {margin-top : 14px }
315+ /* compare molecules */
316+ .cmp-inputs {display : flex;flex-wrap : wrap;gap : 10px ;align-items : center;margin : 0 0 12px }
317+ .cmp-in {flex : 1 ;min-width : 200px ;font : inherit;font-size : 13.5px ;padding : 9px 12px ;border : 1px solid var (--line );border-radius : 8px ;background : var (--panel );color : var (--ink )}
318+ .cmp-in : focus {outline : none;border-color : var (--brand-2 )}
319+ .cmp-vs {font-family : 'Cinzel Decorative' , Georgia, serif;color : var (--cream );font-size : 13px }
320+ .cmp-results {display : grid;grid-template-columns : 1fr 1fr ;gap : 14px ;margin-top : 14px }
321+ @media (max-width : 760px ){.cmp-results {grid-template-columns : 1fr }}
322+ .cmp-card {border : 1px solid var (--line );border-radius : 12px ;overflow : hidden;background : # 0F1319 ;min-height : 120px ;cursor : pointer;transition : border-color .14s }
323+ .cmp-card : hover {border-color : var (--brand-2 )}
324+ .cmp-card img {display : block;width : 100% ;height : auto}
325+ .cmp-card .empty {padding : 20px }
315326 .mix-ings {display : flex;flex-direction : column;gap : 6px ;margin-bottom : 8px }
316327 .mix-ing {display : flex;gap : 10px ;align-items : center;padding : 8px ;border : 1px solid var (--line );border-radius : 8px ;background : var (--surface )}
317328 .mix-ing : hover {background : var (--accent-soft )}
@@ -680,6 +691,24 @@ <h1>Flavormancer</h1>
680691 < div id ="mixResults "> </ div >
681692 </ div >
682693
694+ < label class ="toggle "> < input type ="checkbox " id ="cmpToggle "> < span id ="cmpCaret "> ▸</ span > Compare molecules — two reads side by side</ label >
695+ < div id ="cmpPanel " style ="display:none ">
696+ < p class ="mix-explain "> Enter two molecules to see their < b > flavor cards side by side</ b > — compare
697+ taste, aroma, and structure at a glance (great for a reformulation or a chirality pair).</ p >
698+ < div class ="cmp-inputs ">
699+ < input id ="cmpA " class ="cmp-in " autocomplete ="off " placeholder ="name or SMILES — e.g. vanillin ">
700+ < span class ="cmp-vs "> vs</ span >
701+ < input id ="cmpB " class ="cmp-in " autocomplete ="off " placeholder ="name or SMILES — e.g. ethylvanillin ">
702+ < button class ="mix-btn " id ="cmpGo "> Compare</ button >
703+ </ div >
704+ < div class ="mix-examples "> < span > Try:</ span >
705+ < button type ="button " class ="mix-ex cmp-ex " data-a ="vanillin " data-b ="ethylvanillin "> vanillin vs ethyl vanillin</ button >
706+ < button type ="button " class ="mix-ex cmp-ex " data-a ="(R)-carvone " data-b ="(S)-carvone "> R- vs S-carvone</ button >
707+ < button type ="button " class ="mix-ex cmp-ex " data-a ="limonene " data-b ="menthol "> limonene vs menthol</ button >
708+ </ div >
709+ < div id ="cmpResults " class ="cmp-results "> </ div >
710+ </ div >
711+
683712 < div id ="browse " class ="browse ">
684713 < div class ="browse-head "> Or browse the library — pick a category, then a molecule (each card has a 2D/3D toggle)</ div >
685714 < div id ="browseCats " class ="browse-cats "> </ div >
@@ -1924,7 +1953,7 @@ <h4>Software & type</h4>
19241953} ) ;
19251954if ( $ ( 'mixToggle' ) . checked && ! $ ( 'mixRows' ) . children . length ) { mixAddRow ( ) ; mixAddRow ( ) ; } // open by default
19261955// click a bad-combo example -> load its ingredients + process and run the screen
1927- document . querySelectorAll ( '.mix-ex' ) . forEach ( b => b . addEventListener ( 'click' , ( ) => {
1956+ document . querySelectorAll ( '.mix-ex:not(.cmp-ex) ' ) . forEach ( b => b . addEventListener ( 'click' , ( ) => {
19281957 $ ( 'mixRows' ) . innerHTML = '' ;
19291958 b . dataset . ings . split ( '|' ) . forEach ( s => mixAddRow ( s ) ) ;
19301959 document . querySelectorAll ( '.proc' ) . forEach ( c => { c . checked = ( b . dataset . proc || '' ) . split ( ',' ) . includes ( c . value ) ; } ) ;
@@ -1934,6 +1963,22 @@ <h4>Software & type</h4>
19341963 const card = e . target . closest ( '.mix-ing[data-smi]' ) ;
19351964 if ( card && card . dataset . smi ) { $ ( 'mixToggle' ) . checked = false ; $ ( 'mixToggle' ) . dispatchEvent ( new Event ( 'change' ) ) ; q . value = card . dataset . smi ; window . scrollTo ( { top :0 , behavior :'smooth' } ) ; run ( ) ; }
19361965} ) ;
1966+
1967+ // --- compare two molecules: side-by-side flavor cards (reuses /api/card) ---
1968+ $ ( 'cmpToggle' ) . addEventListener ( 'change' , e => { $ ( 'cmpPanel' ) . style . display = e . target . checked ?'block' :'none' ; $ ( 'cmpCaret' ) . textContent = e . target . checked ?'▾' :'▸' ; } ) ;
1969+ attachRichSuggest ( $ ( 'cmpA' ) ) ; attachRichSuggest ( $ ( 'cmpB' ) ) ;
1970+ function cmpRun ( ) {
1971+ const a = $ ( 'cmpA' ) . value . trim ( ) , b = $ ( 'cmpB' ) . value . trim ( ) ;
1972+ if ( ! a || ! b ) { $ ( 'cmpResults' ) . innerHTML = '<div class="empty">Enter two molecules to compare.</div>' ; return ; }
1973+ $ ( 'cmpResults' ) . innerHTML = [ a , b ] . map ( m => { const e = m . replace ( / " / g, '"' ) ;
1974+ return `<div class="cmp-card" data-q="${ e } " title="Open the full read"><img loading="lazy" src="/api/card?q=${ encodeURIComponent ( m ) } " alt="flavor card for ${ e } " onerror="this.parentElement.textContent='No read for that molecule'"></div>` ;
1975+ } ) . join ( '' ) ;
1976+ }
1977+ $ ( 'cmpGo' ) . addEventListener ( 'click' , cmpRun ) ;
1978+ $ ( 'cmpA' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) cmpRun ( ) ; } ) ;
1979+ $ ( 'cmpB' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) cmpRun ( ) ; } ) ;
1980+ $ ( 'cmpResults' ) . addEventListener ( 'click' , e => { const c = e . target . closest ( '.cmp-card[data-q]' ) ; if ( c ) { q . value = c . dataset . q ; window . scrollTo ( { top :0 , behavior :'smooth' } ) ; run ( ) ; } } ) ;
1981+ document . querySelectorAll ( '.cmp-ex' ) . forEach ( b => b . addEventListener ( 'click' , ( ) => { $ ( 'cmpA' ) . value = b . dataset . a ; $ ( 'cmpB' ) . value = b . dataset . b ; cmpRun ( ) ; } ) ) ;
19371982$ ( 'mixCheck' ) . addEventListener ( 'click' , async ( ) => {
19381983 const ings = [ ...$ ( 'mixRows' ) . querySelectorAll ( 'input' ) ] . map ( i => i . value . trim ( ) ) . filter ( Boolean ) ;
19391984 const procs = [ ...document . querySelectorAll ( '.proc:checked' ) ] . map ( c => c . value ) ;
0 commit comments