77< style >
88 : root {
99 --surface : # F4F6F4 ; --panel : # FFFFFF ; --line : # DCE3DE ; --ink : # 172019 ;
10- --muted : # 5C6B63 ; --accent : # 0F5C4D ; --accent-soft : # 0F5C4D14 ;
10+ --muted : # 5C6B63 ; --accent : # 2A7C86 ; --accent-soft : # 2A7C8614 ;
11+ --brand-1 : # 3A2E7C ; --brand-2 : # 1AA0A8 ; --brand-accent : # E0913C ;
12+ --brand-grad : linear-gradient (100deg , # 3A2E7C 0% , # 2A5E9E 45% , # 1AA0A8 100% );
1113 --sweet : # E0972E ; --bitter : # 6B4E9E ; --umami : # C0563B ; --sour : # 9FB024 ; --salty : # 4E84B0 ; --aroma : # 2E7D6B ;
1214 --ui : ui-sans-serif, system-ui, -apple-system, "Segoe UI" , Roboto, sans-serif;
1315 --mono : ui-monospace, "SF Mono" , Menlo, Consolas, monospace;
1416 }
1517 * {box-sizing : border-box}
1618 body {margin : 0 ;background : var (--surface );color : var (--ink );font-family : var (--ui );
1719 font-size : 15px ;line-height : 1.5 ;-webkit-font-smoothing : antialiased}
18- header {padding : 22px 28px ;border-bottom : 1px solid var (--line );display : flex;
19- align-items : baseline;gap : 14px }
20- header h1 {margin : 0 ;font-size : 18px ;font-weight : 650 ;letter-spacing : -.01em }
20+ header {padding : 16px 28px ;border-bottom : 1px solid var (--line );display : flex;
21+ align-items : center;gap : 15px ;background : linear-gradient (180deg , # FBFCFB, # F4F6F4 )}
22+ header .logo {width : 54px ;height : 54px ;flex : 0 0 auto}
23+ header .brand {display : flex;flex-direction : column;gap : 1px ;min-width : 0 }
24+ header h1 {margin : 0 ;font-size : 23px ;font-weight : 750 ;letter-spacing : -.01em ;line-height : 1.1 ;
25+ background : var (--brand-grad );-webkit-background-clip : text;background-clip : text;color : transparent}
2126 header .sub {font-family : var (--mono );font-size : 12px ;color : var (--muted )}
2227 main {max-width : 980px ;margin : 0 auto;padding : 26px 28px 60px }
2328 .bar {display : flex;gap : 10px ;align-items : stretch}
2429 .bar input {flex : 1 ;font-family : var (--mono );font-size : 15px ;padding : 13px 15px ;
2530 border : 1px solid var (--line );border-radius : 9px ;background : var (--panel );color : var (--ink )}
2631 .bar input : focus {outline : 2px solid var (--accent );border-color : transparent}
2732 .bar button {font-family : var (--ui );font-weight : 600 ;font-size : 15px ;padding : 0 22px ;
28- border : none;border-radius : 9px ;background : var (--accent );color : # fff ;cursor : pointer}
33+ border : none;border-radius : 9px ;background : var (--brand-grad );color : # fff ;cursor : pointer}
2934 .bar button : disabled {opacity : .5 ;cursor : default}
3035 .hint {font-family : var (--mono );font-size : 12px ;color : var (--muted );margin : 8px 2px 0 }
3136 .grid {display : grid;grid-template-columns : 1fr 1fr ;gap : 18px ;margin-top : 24px }
161166</ head >
162167< body >
163168< header >
164- < h1 > Flavormancer</ h1 >
165- < span class ="sub "> taste & aroma prediction from chemical structure — before you pour</ span >
169+ < img class ="logo " src ="/static/logo.png " alt ="Flavormancer logo ">
170+ < div class ="brand ">
171+ < h1 > Flavormancer</ h1 >
172+ < span class ="sub "> taste & aroma prediction from chemical structure — before you pour</ span >
173+ </ div >
166174</ header >
167175< main >
168176 < div class ="bar ">
@@ -174,6 +182,19 @@ <h1>Flavormancer</h1>
174182 < b > SMILES</ b > string (e.g. < code > O=Cc1ccc(O)c(OC)c1</ code > ). SMILES omit implicit
175183 hydrogens — water is just < code > O</ code > . These models are for < b > organic</ b > molecules.</ div >
176184
185+ < label class ="toggle "> < input type ="checkbox " id ="mixToggle "> Mixture mode — screen 2+ ingredients for documented hazards</ label >
186+ < div id ="mixPanel " style ="display:none ">
187+ < div id ="mixRows "> </ div >
188+ < button class ="mix-add " id ="mixAdd "> + add ingredient</ button >
189+ < div class ="mix-proc "> Process applied:
190+ < label > < input type ="checkbox " class ="proc " value ="high_heat "> high heat</ label >
191+ < label > < input type ="checkbox " class ="proc " value ="refining "> refining</ label >
192+ < label > < input type ="checkbox " class ="proc " value ="fermentation "> fermentation</ label >
193+ </ div >
194+ < button class ="mix-btn " id ="mixCheck "> Check mixture</ button >
195+ < div id ="mixResults "> </ div >
196+ </ div >
197+
177198 < div id ="browse " class ="browse ">
178199 < div class ="browse-head "> Or browse the library — pick a category, then a molecule
179200 < span class ="browse-toggle "> < button type ="button " id ="bmode2d " class ="st-tab on "> 2D</ button > < button type ="button " id ="bmode3d " class ="st-tab "> 3D</ button > </ span >
@@ -192,20 +213,6 @@ <h1>Flavormancer</h1>
192213 </ div >
193214 </ div >
194215
195- < label class ="toggle "> < input type ="checkbox " id ="mixToggle "> Mixture mode — screen 2+ ingredients for documented hazards</ label >
196-
197- < div id ="mixPanel " style ="display:none ">
198- < div id ="mixRows "> </ div >
199- < button class ="mix-add " id ="mixAdd "> + add ingredient</ button >
200- < div class ="mix-proc "> Process applied:
201- < label > < input type ="checkbox " class ="proc " value ="high_heat "> high heat</ label >
202- < label > < input type ="checkbox " class ="proc " value ="refining "> refining</ label >
203- < label > < input type ="checkbox " class ="proc " value ="fermentation "> fermentation</ label >
204- </ div >
205- < button class ="mix-btn " id ="mixCheck "> Check mixture</ button >
206- < div id ="mixResults "> </ div >
207- </ div >
208-
209216 < div id ="err " class ="err " style ="display:none "> </ div >
210217
211218 < div class ="grid " id ="results " style ="display:none ">
@@ -267,6 +274,18 @@ <h2>Aroma</h2>
267274const TASTE_COLORS = { sweet :'var(--sweet)' , bitter :'var(--bitter)' , umami :'var(--umami)' ,
268275 sour :'var(--sour)' , salty :'var(--salty)' } ;
269276const MODEL_TASTES = [ 'sweet' , 'bitter' , 'umami' ] ;
277+ // a distinct color per aroma descriptor, so each note reads at a glance
278+ const AROMA_COLORS = { fruity :'#E8913C' , citrus :'#EFC01F' , floral :'#E86AA6' , minty :'#2FB37E' ,
279+ sweet :'#E0972E' , woody :'#9C6B3F' , spicy :'#D9534F' , almond :'#C9A66B' , vanilla :'#D6A93C' ,
280+ camphor :'#4FB0C4' , medicinal :'#7E57C2' , ammoniacal :'#8A97A3' , ethereal :'#66C2D6' ,
281+ pungent :'#E4572E' , garlic :'#8FA31E' , fishy :'#4F8FC0' , sulfurous :'#B8A93A' , rose :'#E07A9E' ,
282+ herbal :'#5AA469' , green :'#6DB33F' , earthy :'#8B7355' , caramel :'#C68A3E' , nutty :'#B0813F' ,
283+ buttery :'#E6C34D' , balsamic :'#9A6B8C' , smoky :'#7A6B5D' , anise :'#C05A6E' , coconut :'#CBB994' ,
284+ cinnamon :'#B5561F' , clove :'#8A5A2B' , coffee :'#6A4632' , cocoa :'#6B4226' , meaty :'#A0522D' ,
285+ cheesy :'#D9B84D' , creamy :'#E3CE9A' , waxy :'#C4BE96' , fresh :'#4FC0B0' , grassy :'#7DAF3A' ,
286+ rancid :'#9A8B5A' , tarry :'#4A4038' , winey :'#8E4B6E' , honey :'#E0A83C' , apple :'#8DBF4A' ,
287+ cherry :'#C0392B' , pine :'#3FA06B' , banana :'#E6C84D' } ;
288+ const aromaColor = n => AROMA_COLORS [ n ] || 'var(--aroma)' ;
270289
271290const $ = id => document . getElementById ( id ) ;
272291const go = $ ( 'go' ) , q = $ ( 'q' ) ;
@@ -377,7 +396,7 @@ <h2>Aroma</h2>
377396 ${ x . iupac ?`<div class="nb-iupac">${ x . iupac } </div>` :'' }
378397 <div class="s">${ x . smiles } </div>
379398 <div class="kt">${ x . known_tastes . length ?x . known_tastes . join ( ' · ' ) :'—' } </div>
380- ${ ( x . aroma && x . aroma . length ) ?`<div class="nb-aroma" title="aroma (found = documented, else predicted)">${ x . aroma . map ( a => `<span class="atag">${ a . odor } ${ a . source === 'predicted' ?' •' :'' } </span>` ) . join ( '' ) } </div>` :'' }
399+ ${ ( x . aroma && x . aroma . length ) ?`<div class="nb-aroma" title="aroma (found = documented, else predicted • )">${ x . aroma . map ( a => `<span class="atag" style="color: ${ aromaColor ( a . odor ) } ;border-color: ${ aromaColor ( a . odor ) } 44 ">${ a . odor } ${ a . source === 'predicted' ?' •' :'' } </span>` ) . join ( '' ) } </div>` :'' }
381400 </div>
382401 <div class="sim">${ Math . round ( x . similarity * 100 ) } % match</div>
383402 </div>` ) . join ( '' ) ;
@@ -444,7 +463,7 @@ <h2>Aroma</h2>
444463 if ( pred && ( pred . descriptors || [ ] ) . length ) {
445464 const lowconf = pred . any_confident === false ;
446465 html += `<div class="aroma-prev-tag">Predicted descriptors · model (presence/absence, public-domain HSDB)${ lowconf ?' — low confidence (no strong descriptor)' :'' } </div>` ;
447- html += pred . descriptors . map ( d => { const pct = Math . round ( d . score * 100 ) ; const au = d . auroc ?` · AUROC ${ d . auroc } ` :'' ; return `<div class="ameter"><div class="arow"><span class="aname">${ d . odor } </span><span class="aval">${ pct } %${ au } </span></div><div class="atrack"><div class="afill" style="width:${ pct } %"></div></div></div>` ; } ) . join ( '' ) ;
466+ html += pred . descriptors . map ( d => { const pct = Math . round ( d . score * 100 ) ; const col = aromaColor ( d . odor ) ; const au = d . auroc ?` · AUROC ${ d . auroc } ` :'' ; return `<div class="ameter"><div class="arow"><span class="aname" style="color: ${ col } " >${ d . odor } </span><span class="aval">${ pct } %${ au } </span></div><div class="atrack"><div class="afill" style="width:${ pct } %;background: ${ col } "></div></div></div>` ; } ) . join ( '' ) ;
448467 }
449468 const doc = ar . documented ;
450469 if ( doc && ( doc . notes || [ ] ) . length ) {
0 commit comments