|
93 | 93 | .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} |
94 | 94 | .mix-btn:disabled{opacity:.5} |
95 | 95 | #mixResults{margin-top:14px} |
| 96 | + .mix-ings{display:flex;flex-direction:column;gap:6px;margin-bottom:8px} |
| 97 | + .mix-ing{display:flex;gap:10px;align-items:center;padding:8px;border:1px solid var(--line);border-radius:8px;background:var(--surface)} |
| 98 | + .mix-ing:hover{background:var(--accent-soft)} |
96 | 99 | .hazard{border:1px solid #E7C4B8;background:#FBEDE9;color:#8A3A22;border-radius:8px;padding:10px 13px;margin-bottom:8px;font-size:13px;line-height:1.5} |
97 | 100 | .hazard.cond{border-color:#E8D38A;background:#FFF6E6;color:#7A5A12} |
98 | 101 | .suggest-dd{position:absolute;z-index:60;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:0 8px 24px #00000022;max-height:340px;overflow:auto} |
@@ -396,18 +399,42 @@ <h2>Aroma</h2> |
396 | 399 | ['results','behaviorCard','aromaCard','footnote','err'].forEach(id=>$(id).style.display='none'); |
397 | 400 | if(on && !$('mixRows').children.length){ mixAddRow(); mixAddRow(); } |
398 | 401 | }); |
| 402 | +$('mixResults').addEventListener('click', e=>{ |
| 403 | + const card=e.target.closest('.mix-ing[data-smi]'); |
| 404 | + 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(); } |
| 405 | +}); |
399 | 406 | $('mixCheck').addEventListener('click', async ()=>{ |
400 | 407 | const ings=[...$('mixRows').querySelectorAll('input')].map(i=>i.value.trim()).filter(Boolean); |
401 | 408 | const procs=[...document.querySelectorAll('.proc:checked')].map(c=>c.value); |
402 | 409 | if(ings.length<2){ $('mixResults').innerHTML='<div class="empty">Add at least two ingredients.</div>'; return; } |
403 | 410 | $('mixCheck').disabled=true; $('mixCheck').textContent='Checking…'; |
404 | 411 | try{ |
405 | 412 | const r=await post('/api/mixture',{ingredients:ings, processes:procs}); |
406 | | - const A=r.active_hazards||[], C=r.conditional_hazards||[]; |
407 | 413 | let html=''; |
408 | | - if(!A.length && !C.length) html='<div class="empty">No documented hazards for these ingredients/processes. (Absence of a flag is not a safety clearance.)</div>'; |
| 414 | + const ing=r.ingredients||[]; |
| 415 | + if(ing.length){ |
| 416 | + html+='<div class="bsub">Ingredients (read individually)</div><div class="mix-ings">'+ing.map(it=>` |
| 417 | + <div class="mix-ing"> |
| 418 | + <div class="nb-struct">${it.svg||''}</div> |
| 419 | + <div class="nb-info"><div class="nb-name">${it.name||it.smiles}</div> |
| 420 | + <div class="kt">${(it.tastes||[]).join(' · ')||'—'}${(it.gras||'').indexOf('in GRAS')===0?' · GRAS':''}</div> |
| 421 | + ${((it.alerts||[]).length||(it.tox_flags||[]).length)?`<div class="kt" style="color:#8A3A22">⚠ ${[...(it.alerts||[]),...(it.tox_flags||[]).map(f=>'tox:'+f)].join(', ')}</div>`:''} |
| 422 | + </div></div>`).join('')+'</div>'; |
| 423 | + } |
| 424 | + const A=r.active_hazards||[], C=r.conditional_hazards||[]; |
| 425 | + html+='<div class="bsub">Documented mixture hazards</div>'; |
| 426 | + if(!A.length && !C.length) html+='<div class="empty">None documented for these ingredients/processes. (Not a safety clearance.)</div>'; |
409 | 427 | A.forEach(h=>html+=`<div class="hazard">⚠ <b>${h.possible_product}</b> — from ${h.precursors.join(' + ')}. ${h.note}</div>`); |
410 | 428 | C.forEach(h=>html+=`<div class="hazard cond">○ <b>${h.possible_product}</b> — from ${h.precursors.join(' + ')}; <b>would form with</b>: ${(h.requires_process||[]).join(', ')}. ${h.note}</div>`); |
| 429 | + const pal=r.palette||{}, M=pal.matches||[]; |
| 430 | + if(M.length){ |
| 431 | + html+=`<div class="bsub">Single molecules with a similar taste palette (${(pal.target||[]).join(' · ')||'—'})</div><div class="mix-ings">`+M.map(it=>` |
| 432 | + <div class="mix-ing" data-smi="${it.smiles}" style="cursor:pointer" title="Click to analyze"> |
| 433 | + <div class="nb-struct">${it.svg||''}</div> |
| 434 | + <div class="nb-info"><div class="nb-name">${it.name||it.smiles}</div><div class="kt">${(it.tastes||[]).join(' · ')}</div></div> |
| 435 | + <div class="sim">${Math.round(it.match*100)}% match</div></div>`).join('')+'</div>'; |
| 436 | + } |
| 437 | + html+='<div class="empty" style="font-size:12px">Each ingredient is read individually and screened for documented hazards. How the blend actually <i>tastes</i> together (suppression / synergy) needs your formulation data — not predictable from public data.</div>'; |
411 | 438 | $('mixResults').innerHTML=html; |
412 | 439 | }catch(e){ $('mixResults').innerHTML='<div class="err">'+e.message+'</div>'; } |
413 | 440 | finally{ $('mixCheck').disabled=false; $('mixCheck').textContent='Check mixture'; } |
|
0 commit comments