1<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Aug 28 2026 15:00:00 GMT+0000 (Coordinated Universal Time) --><html data-wf-domain="aksomaitis.webflow.io" data-wf-page="67bcb490a5ba2f19e0eb22fe" data-wf-site="679d63c04202ad9bf67ca282" data-wf-status="1" lang="en"><head><meta charset="utf-8"/><link href="https://cdn.prod.website-files.com" rel="preconnect" crossorigin="anonymous"/><title>JLAM AI Redesign â AI as a tool, not a feature</title><meta content="A graduation UX case study: redesigning JLAM's community app so AI supports dietary change ethically â user-owned health data, transparent AI and a meal tool validated at 4.40/5 across three test rounds." name="description"/><meta content="JLAM AI Redesign â AI as a tool, not a feature" property="og:title"/><meta content="A graduation UX case study: redesigning JLAM's community app so AI supports dietary change ethically â user-owned health data, transparent AI and a meal tool validated at 4.40/5 across three test rounds." property="og:description"/><meta content="JLAM AI Redesign â AI as a tool, not a feature" name="twitter:title"/><meta content="A graduation UX case study: redesigning JLAM's community app so AI supports dietary change ethically â user-owned health data, transparent AI and a meal tool validated at 4.40/5 across three test rounds." name="twitter:description"/><meta property="og:type" content="website"/><meta content="summary_large_image" name="twitter:card"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta content="Webflow" name="generator"/><link href="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/css/aksomaitis.webflow.shared.293bfe606.css" rel="stylesheet" type="text/css" integrity="sha384-KTv+YG0V3I2PBSdU+gvrEHTdCuNOR1+z0VxsCOpBAKhxC1t73cOqUGW5nY6/W+kP" crossorigin="anonymous"/><style>@media (min-width:992px) {html.w-mod-js:not(.w-mod-ix) [data-w-id="02c5af0e-7768-1787-35c5-c7b0edee3a80"] {-webkit-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-moz-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-ms-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);}html.w-mod-js:not(.w-mod-ix) [data-w-id="07f11707-c556-8739-f69a-9a43475c311c"] {-webkit-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-moz-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-ms-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);}html.w-mod-js:not(.w-mod-ix) [data-w-id="07f11707-c556-8739-f69a-9a43475c3118"] {-webkit-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);-moz-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);-ms-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);}html.w-mod-js:not(.w-mod-ix) [data-w-id="07f11707-c556-8739-f69a-9a43475c312d"] {-webkit-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-moz-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-ms-transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);transform:translate3d(0, 100%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);}html.w-mod-js:not(.w-mod-ix) [data-w-id="07f11707-c556-8739-f69a-9a43475c3129"] {-webkit-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);-moz-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);-ms-transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);transform:translate3d(0, 0%, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0deg);}}</style>
1<link href="https://fonts.googleapis.com" rel="preconnect"/><link href="https://fonts.gstatic.com" rel="preconnect" crossorigin="anonymous"/>
1<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js" type="text/javascript"></script>
1<script type="text/javascript">WebFont.load({ google: { families: ["Exo:100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic","Poppins:400,500,600"] }});</script>
1<script type="text/javascript">!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);</script>
1<link href="https://cdn.prod.website-files.com/img/favicon.ico" rel="shortcut icon" type="image/x-icon"/><link href="https://cdn.prod.website-files.com/img/webclip.png" rel="apple-touch-icon"/><style> 2/* Page transition curtain. Lives on html's pseudo-elements so it exists before 3 any content paints. z-index 85 sits under the nav (90) and AI fab (9990) so 4 both stay visible while the page underneath fades. 5 Two layers on purpose: ::before fades IN by animation (and so reveals the page 6 even if JS never runs), ::after fades OUT by transition. Cancelling a 7 forwards-filling animation snaps the value instead of transitioning it, so one 8 shared layer cannot do both. */ 9html::before,html::after{content:'';position:fixed;inset:0;background:#000; 10 z-index:85;pointer-events:none} 11html::before{opacity:1;animation:axptO .55s ease .05s forwards} 12html::after{opacity:0;transition:opacity .4s ease} 13html.axpt-out::after{opacity:1} 14@keyframes axptO{to{opacity:0}} 15@media(prefers-reduced-motion:reduce){ 16 html::before{animation:none;opacity:0} 17 html::after{transition:none} 18} 19/* Mobile: the nav is a fixed pill with margin-top 1.2em and content scrolls 20 under it, so the gap down to a hero title is set entirely by that margin. 21 Easing it up buys clearance on every page without touching page layout. */ 22@media(max-width:767px){#nv-root{margin-top:.55em}} 23</style>
24<script> 25(function(){ 26var h=document.documentElement,T=400; 27var rm=window.matchMedia&&matchMedia('(prefers-reduced-motion:reduce)').matches; 28function off(){h.classList.remove('axpt-out')} 29addEventListener('pageshow',off); 30addEventListener('popstate',off); 31/* bubble phase on purpose: anything that already handled the click 32 (in-page filters, the nav CTA scroll, the AI panel) sets defaultPrevented 33 and we leave it alone */ 34document.addEventListener('click',function(e){ 35if(e.defaultPrevented||e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return; 36var a=e.target&&e.target.closest?e.target.closest('a'):null; 37if(!a||!a.href)return; 38if(a.target&&a.target!=='_self')return; 39if(a.hasAttribute('download'))return; 40var raw=a.getAttribute('href')||''; 41if(!raw||raw.charAt(0)==='#')return; 42if(/^(mailto:|tel:|javascript:)/i.test(raw))return; 43var u;try{u=new URL(a.href,location.href)}catch(x){return} 44if(u.origin!==location.origin)return; 45if(u.pathname===location.pathname&&u.search===location.search)return; 46if(rm)return; 47e.preventDefault(); 48h.classList.add('axpt-out'); 49setTimeout(function(){location.href=u.href},T); 50}); 51})(); 52</script>
vendor: 1 bytes, line 52
52
53<script> 54(function(){ 55/* He peeks up from behind a button on hover, with a heart over his head, and 56 sinks away when you leave. Nothing below the button's top edge is ever drawn: 57 the sprite is clipped to that line, so on the outlined phone button - which 58 you can see straight through - no body shows inside the button itself. 59 Lives in the site head; #axz-root keeps it clear of the homepage mascot. */ 60if(!matchMedia('(min-width:992px)').matches 61||!matchMedia('(pointer:fine)').matches 62||matchMedia('(prefers-reduced-motion:reduce)').matches)return; 63 64var SEL='#ct-root a.ct-btn, .cs-next a, .cs-next button'; 65var W=78,H=69,HS=42,STIFF=110,DAMP=15; /* HS = headroom above him for the heart */ 66/* Fraction of him above the edge. Eyes sit 56% down the sprite, so under ~0.7 67 clips his face off and leaves antennae only. 0.8 shows the whole face. */ 68var PEEK=.8; 69function N(v){return Math.round(v*10)/10} 70function U(v){return v<0?0:v>1?1:v} 71/* same two-cubic face as the mascot and the chat-header avatar */ 72function sh(cx,w,y,u,d){var x=N(cx-w),z=N(cx+w),t=N(y-u*1.3333),b=N(y+d*1.3333);y=N(y); 73return'M'+x+' '+y+'C'+x+' '+t+' '+z+' '+t+' '+z+' '+y+'C'+z+' '+b+' '+x+' '+b+' '+x+' '+y+'Z'} 74function eyeD(cx,a){return sh(cx,25-4*a,134+9*a,25-6.5*a,25-34.5*a)} 75function mouthD(o){return sh(138,31+3*o,168-6*o,0,29+12*o)} 76 77var root=document.createElement('div'); 78root.id='axz-root'; 79root.setAttribute('aria-hidden','true'); 80root.style.cssText='position:fixed;left:0;top:0;width:'+W+'px;height:'+(H+HS)+'px;'+ 81'z-index:80;pointer-events:none;opacity:0;will-change:transform,opacity,clip-path;display:none'; 82root.innerHTML= 83'<div id="zheart" style="position:absolute;left:50%;top:0;width:30px;height:30px;'+ 84'margin-left:-15px;opacity:0;will-change:transform,opacity">'+ 85'<svg viewBox="0 0 24 24" style="width:100%;height:100%;display:block">'+ 86'<path d="M12 21s-8-5.4-8-11a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 10c0 5.6-8 11-8 11z" fill="#FF6FA5"/>'+ 87'</svg></div>'+ 88'<div id="zbot" style="position:absolute;left:0;bottom:0;width:'+W+'px;height:'+H+'px">'+ 89'<svg viewBox="0 0 274 240" style="width:100%;height:100%;display:block;overflow:visible">'+ 90'<g fill="#B173FE">'+ 91'<g><path d="M96 12 79 33 89 33 84 47 101 26 91 26Z"/><rect x="87" y="40" width="5" height="20" rx="2.5"/><circle cx="90" cy="60" r="9"/></g>'+ 92'<g><path d="M195 8 178 29 188 29 183 43 200 22 190 22Z"/><rect x="185" y="40" width="5" height="20" rx="2.5"/><circle cx="188" cy="60" r="9"/></g>'+ 93'<rect x="7" y="117" width="31" height="70" rx="11"/>'+ 94'<rect x="238" y="117" width="31" height="70" rx="11"/>'+ 95'<rect x="45" y="67" width="186" height="168" rx="45"/></g>'+ 96'<g id="zbl" fill="#FF6FA5" opacity="0"><ellipse cx="74" cy="176" rx="17" ry="9"/>'+ 97'<ellipse cx="202" cy="176" rx="17" ry="9"/></g>'+ 98'<path id="ze1" fill="#9CDCFE"/><path id="ze2" fill="#9CDCFE"/>'+ 99'<g id="zp1"><circle cx="92" cy="134" r="12" fill="#141414"/>'+ 100'<circle cx="87" cy="128" r="4.6" fill="#fff"/><circle cx="97" cy="139" r="2.3" fill="#fff"/></g>'+ 101'<g id="zp2"><circle cx="183" cy="134" r="12" fill="#141414"/>'+ 102'<circle cx="178" cy="128" r="4.6" fill="#fff"/><circle cx="188" cy="139" r="2.3" fill="#fff"/></g>'+ 103'<path id="zmo" fill="#9CDCFE"/></svg></div>'; 104 105var E={},raf=0,lt=0; 106function boot(){ 107document.body.appendChild(root); 108['zbl','ze1','ze2','zp1','zp2','zmo','zheart'].forEach(function(i){E[i]=root.querySelector('#'+i)});} 109 110var px=0,py=0,vx=0,vy=0,tx=0,ty=0,op=0,top_=0, 111a=0,ta=0,o=0,to=0,bk=0,bt=2.5,hb=0, 112mx=0,my=0,tgt=null,live=false,line=0; 113 114function rect(el){var r=el.getBoundingClientRect(); 115return{l:r.left,t:r.top,r:r.right,w:r.width,h:r.height}} 116 117function aim(){ 118if(!tgt||!tgt.isConnected)return; 119var r=rect(tgt); 120line=r.t; /* the edge he hides behind */ 121/* he slides along the button under your cursor rather than sitting dead centre */ 122var pad=Math.min(W*.75,r.w*.3); 123tx=Math.max(r.l+pad,Math.min(r.r-pad,mx)); 124ty=r.t-H*(PEEK-.5); /* PEEK of him above the edge */ 125} 126 127function enter(el){ 128if(tgt===el&&live)return; 129/* Switching buttons must not slide him across the gap - he drops out of sight 130 and pops up fresh. Resetting opacity is what makes him vanish, not travel. */ 131var moved=tgt!==el; 132tgt=el;live=true; 133root.style.display='block'; 134aim(); 135if(moved||op<.05){px=tx;py=line+H;vx=vy=0;op=0;root.style.opacity=0} 136top_=1;ta=1;to=0;hb=0; 137run()} 138function leave(){ 139if(!live)return; 140live=false;top_=0;ta=0;to=0; 141if(tgt){var r=rect(tgt);ty=r.t+H*.9} /* sink back down out of sight */ 142run()} 143 144function frame(now){ 145raf=requestAnimationFrame(frame); 146var dt=lt?Math.min((now-lt)/1000,.05):.016;lt=now; 147if(live)aim(); 148vx+=((tx-px)*STIFF-vx*DAMP)*dt;vy+=((ty-py)*STIFF-vy*DAMP)*dt; 149px+=vx*dt;py+=vy*dt; 150op+=(top_-op)*Math.min(1,dt*(live?11:9)); 151var k=Math.min(1,dt*13);a+=(ta-a)*k;o+=(to-o)*k; 152bt-=dt;if(bt<=0){if(a<.3)bk=.16;bt=3+Math.random()*4} 153if(bk>0)bk-=dt; 154if(live)hb+=dt; 155paint(); 156/* park only once he is actually gone, and wipe the inline opacity so nothing 157 can be left frozen mid-fade */ 158if(!live&&op<.02){cancelAnimationFrame(raf);raf=0;lt=0;tgt=null; 159op=0;root.style.opacity=0;root.style.display='none'}} 160function run(){if(raf)return;lt=0;raf=requestAnimationFrame(frame)} 161 162function paint(){ 163var rt=py-H/2-HS; /* top of the root box */ 164root.style.opacity=op; 165root.style.transform='translate3d('+N(px-W/2)+'px,'+N(rt)+'px,0)'; 166/* clip everything below the button's top edge: this is what keeps his body off 167 the see-through outlined button */ 168var b=U(((rt+HS+H)-line)/(H+HS))*(H+HS); 169root.style.clipPath='inset(0px 0px '+N(b)+'px 0px)'; 170/* heart: pops in, then bobs and drifts */ 171var hs=U(op*1.4),bob=Math.sin(hb*3.1)*3; 172E.zheart.style.opacity=hs*.95; 173E.zheart.style.transform='translateY('+N(10+bob)+'px) scale('+N(.5+hs*.5)+')'; 174var by=bk>0?1-Math.sin((1-bk/.16)*3.1416)*.86:1;
175E.ze1.setAttribute('d',eyeD(92,a));E.ze2.setAttribute('d',eyeD(183,a)); 176E.zmo.setAttribute('d',mouthD(o)); 177/* pupils follow the cursor - he watches you while he peeks */ 178var cx=px,cy=py,gx=mx-cx,gy=my-cy,dl=Math.sqrt(gx*gx+gy*gy)||1, 179dx=gx/dl*Math.min(7,dl*.05),dy=gy/dl*Math.min(6,dl*.045); 180var ps=N(Math.max(0,1-a*1.4)),sy=N(ps*by),t='translate('+N(dx)+','+N(dy)+')'; 181E.zp1.setAttribute('transform',t+'translate(92,134)scale('+ps+','+sy+')translate(-92,-134)'); 182E.zp2.setAttribute('transform',t+'translate(183,134)scale('+ps+','+sy+')translate(-183,-134)'); 183E.ze1.setAttribute('transform','translate(92,134)scale(1,'+N(by)+')translate(-92,-134)'); 184E.ze2.setAttribute('transform','translate(183,134)scale(1,'+N(by)+')translate(-183,-134)'); 185E.zbl.setAttribute('opacity',N(a*.8))} 186 187/* delegated, so it survives anything rendered late */ 188addEventListener('mousemove',function(e){mx=e.clientX;my=e.clientY},{passive:true}); 189document.addEventListener('mouseover',function(e){ 190var el=e.target&&e.target.closest?e.target.closest(SEL):null; 191if(el)enter(el)},{passive:true}); 192document.addEventListener('mouseout',function(e){ 193var el=e.target&&e.target.closest?e.target.closest(SEL):null; 194if(!el)return; 195var to_=e.relatedTarget; 196if(to_&&to_.closest&&to_.closest(SEL)===el)return; /* moved within the button */ 197leave()},{passive:true}); 198addEventListener('scroll',function(){if(live)aim()},{passive:true}); 199 200window.__AXZ={enter:enter,leave:leave,root:root,sel:SEL, 201state:function(){return{live:live,op:+op.toFixed(3),x:Math.round(px),y:Math.round(py), 202tx:Math.round(tx),ty:Math.round(ty),line:Math.round(line),a:+a.toFixed(2), 203clip:root.style.clipPath,heart:E.zheart?E.zheart.style.opacity:null}}}; 204 205if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot);else boot(); 206})(); 207</script>
207<style> 208/*width*/ 209::-webkit-scrollbar { 210width:0px; 211} 212</style>
212<script type="text/javascript">window.__WEBFLOW_CURRENCY_SETTINGS = {"currencyCode":"USD","symbol":"$","decimal":".","fractionDigits":2,"group":",","template":"{{wf {\"path\":\"symbol\",\"type\":\"PlainText\"} }} {{wf {\"path\":\"amount\",\"type\":\"CommercePrice\"} }} {{wf {\"path\":\"currencyCode\",\"type\":\"PlainText\"} }}","hideDecimalForWholeNumbers":false};</script>
212</head><body><div class="page-wrapper"><div data-animation="default" data-collapse="none" data-duration="400" data-easing="ease" data-easing2="ease" role="banner" class="navbar w-nav"><div class="navbar-container w-container"><a href="/" class="home-link w-inline-block"><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/67b38d6e61ff1d1f429d908a_site%20logo.svg" loading="lazy" alt="Armandas Aksomaitis logo" class="logo-icon"/></a><nav role="navigation" class="nav-menu w-nav-menu"><a href="/work" data-w-id="ac142b4e-6305-1e49-8f6f-13ba5d5fc8e5" class="navbar-link w-inline-block"><div class="nav-link-text">Work</div><div class="nav-link-text">Work</div></a><a href="#Contact" data-w-id="ac142b4e-6305-1e49-8f6f-13ba5d5fc8ea" class="navbar-link last-nav-link w-inline-block"><div class="nav-link-text">Contact</div><div class="nav-link-text">Contact</div></a></nav></div></div><div class="w-embed w-script"><style> 213.navbar.w-nav{display:none!important} 214:where(#nv-root,#nv-root *){box-sizing:border-box;margin:0;padding:0} 215#nv-root{position:fixed;top:0;left:0;right:0;z-index:90;margin-top:1.2em;font-family:'Poppins',sans-serif;font-size:16px;transition:transform .32s ease,opacity .32s ease;will-change:transform} 216#nv-root.nv-hide{transform:translateY(calc(-100% - 1.4em));opacity:0;pointer-events:none} 217#nv-root .nv-wrap{width:83.33%;margin:0 auto;display:flex;align-items:center;gap:26px;padding:9px 9px 9px 26px;background:rgba(10,10,10,.66);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.14);border-radius:2em} 218#nv-root .nv-logo{font-weight:700;font-size:16.5px;color:#fff;text-decoration:none;margin-right:auto} 219#nv-root a.nv-t{position:relative;z-index:0;display:inline-flex;align-items:center;color:#e6e6e6;text-decoration:none;font-size:13.5px;font-weight:500;padding:8px 14px 8px 28px;border-radius:2em;transition:color .25s ease,padding .25s ease} 220#nv-root a.nv-t::before{content:'';position:absolute;z-index:-1;left:12px;top:50%;width:8px;height:8px;margin-top:-4px;border-radius:2em;background:var(--c);transition:left .25s ease,top .25s ease,width .25s ease,height .25s ease,margin .25s ease} 221#nv-root a.nv-t:hover{color:#111;font-weight:600;padding:8px 21px} 222#nv-root a.nv-t:hover::before{left:0;top:0;width:100%;height:100%;margin-top:0} 223#nv-root .nv-cta{background:#fff;color:#111;border-radius:2em;padding:11px 24px;text-decoration:none;font-size:13.5px;font-weight:600;transition:transform .2s} 224#nv-root .nv-cta:hover{transform:translateY(-2px)} 225/* the CTA shortens to "Contact" on phones, where the pill has no room */ 226#nv-root .nv-cta .nv-s{display:none} 227@media(max-width:767px){ 228#nv-root .nv-cta .nv-l{display:none} 229#nv-root .nv-cta .nv-s{display:inline} 230} 231@media(max-width:900px){#nv-root a.nv-t{display:none}} 232@media(prefers-reduced-motion:reduce){#nv-root,#nv-root *{transition:none!important}} 233</style> 234<div id='nv-root'><div class='nv-wrap'> 235<a class='nv-logo' href='/'>Armandas Aksomaitis</a> 236<a class='nv-t' style='--c:#99dcff' href='/?flt=ux#Projects'>UX Research</a> 237<a class='nv-t' style='--c:#9f80ff' href='/?flt=ai#Projects'>Web/app & AI</a> 238<a class='nv-t' style='--c:#db2ffb' href='/?flt=vm#Projects'>Visual & Motion</a> 239<a class='nv-t' style='--c:#b6f24a' href='/?flt=ph#Projects'>Photography</a> 240<a class='nv-cta' href='#Contact'><span class='nv-l'>Get in touch</span><span class='nv-s'>Contact</span></a> 241</div></div>
242<script> 243(function(){var nv=document.getElementById('nv-root');if(!nv)return;var ly=Math.max(0,window.pageYOffset),acc=0,tick=false;function upd(){tick=false;var y=Math.max(0,window.pageYOffset),d=y-ly;ly=y;if(y<=10){nv.classList.remove('nv-hide');acc=0;return}if(d>0){acc=acc>0?acc+d:d;if(acc>26)nv.classList.add('nv-hide')}else if(d<0){acc=acc<0?acc+d:d;if(acc<-12)nv.classList.remove('nv-hide')}} 244window.addEventListener('scroll',function(){if(!tick){tick=true;window.requestAnimationFrame(upd)}},{passive:true}); 245Array.prototype.forEach.call(document.querySelectorAll('#nv-root a.nv-t'),function(a){a.addEventListener('click',function(e){var m=(a.getAttribute('href')||'').match(/flt=(\w+)/);if(m&&window.location.pathname==='/'){e.preventDefault();if(window.hwFilter){window.hwFilter(m[1])}var p=document.getElementById('Projects');if(p){p.scrollIntoView({behavior:'smooth'})}}})}); 246var cta=document.querySelector('#nv-root .nv-cta'); 247if(cta){cta.addEventListener('click',function(e){var t=document.querySelector('#ct-root .ct-sec');if(t){e.preventDefault();t.scrollIntoView({behavior:'smooth'})}})} 248})(); 249</script>
249</div><div class="w-embed w-script"><style> 250.project-overview-section{background:#000 url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/67b376c7af70a23327aa6507_67338b961a9ea2c6f169d51b_5da3410a848a16c643da87c6_work2-transformed.jpeg') top center no-repeat;background-size:100% auto;box-shadow:inset 0 0 0 9999px rgba(0,0,0,.35);padding-top:130px;padding-bottom:2.5em!important} 251.project-container{margin-bottom:1.5em!important} 252.contact-page-wrapper{display:none!important} 253.outline-button,.outline-button.item{display:inline-flex!important;align-items:center!important;justify-content:center!important;width:auto!important;height:auto!important;min-width:0!important;padding:16px 38px!important;background:transparent!important;border:2.5px solid #fff!important;border-radius:2em!important;overflow:visible!important;font-size:16px!important;transform:none!important;opacity:1!important;transition:transform .2s,background .2s!important} 254.outline-button:hover{background:#fff!important;transform:translateY(-2px)!important} 255.outline-button .hover-background{display:none!important} 256.outline-button .button-text.dark-text{display:none!important} 257.button-text-slide-container{display:contents!important} 258.outline-button .button-text{display:block!important;width:auto!important;height:auto!important;margin:0!important;color:#fff!important;font-size:16px!important;font-weight:600!important;line-height:1.2!important;text-align:center;transform:none!important;opacity:1!important;transition:color .2s} 259.outline-button:hover .button-text{color:#111!important} 260.w-embed-youtubevideo.vframe{position:relative;border-radius:2.1em;overflow:hidden;border:2px solid var(--acc,#99dcff)} 261.vfx{position:absolute;left:0;top:0;width:100%;height:100%;cursor:pointer;background-size:cover;background-position:center;background-color:#0c0c0c} 262.vfx::before{content:'';position:absolute;inset:0;background:linear-gradient(45deg,transparent 44%,var(--acc,#99dcff)),linear-gradient(180deg,rgba(0,0,0,.28),rgba(0,0,0,.4));opacity:.92} 263.vfx::after{content:'';position:absolute;left:50%;top:50%;width:64px;height:64px;margin:-32px 0 0 -32px;border-radius:50%;background-color:var(--acc,#99dcff);opacity:.96;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9.5 6.5l9 5.5-9 5.5z' fill='%23111'/></svg>");background-repeat:no-repeat;background-position:center;background-size:26px;transition:transform .2s} 264.vfx:hover::after{transform:scale(1.08)} 265.cs-next{width:83.33%;margin:0 auto;font-family:'Poppins',sans-serif;font-size:16px;line-height:1.5} 266.cs-next h2{color:#fff;font-size:clamp(26px,3vw,34px);font-weight:700;margin:0 0 22px} 267.cs-next-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px} 268.cs-pc{position:relative;border-radius:2em;overflow:hidden;background:#181818;border:.12em solid var(--c);display:flex;flex-direction:column;justify-content:flex-end;padding:26px;min-height:280px;text-decoration:none;transition:transform .2s} 269.cs-pc:hover{transform:translateY(-4px)} 270.cs-pc::before{content:'';position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,rgba(0,0,0,.32),rgba(0,0,0,.88) 84%),linear-gradient(45deg,transparent 58%,var(--c)),linear-gradient(0deg,color-mix(in srgb,var(--c) 28%,transparent),color-mix(in srgb,var(--c) 28%,transparent))} 271.cs-pc>*{position:relative;z-index:1} 272.cs-pc .bgp{position:absolute;inset:0;z-index:0;background-size:cover;background-position:center;filter:grayscale(1) contrast(1.08) brightness(.92)} 273.cs-pc .tg{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#e8e8e8;margin-bottom:8px} 274.cs-pc h3{color:#fff;font-weight:700;font-size:21px;line-height:1.2;margin:0 0 6px} 275.cs-pc p{color:#ececec;font-size:13.5px;max-width:34em;margin:0} 276.cs-pc .go{margin-top:14px;font-size:12.5px;font-weight:700;color:#fff} 277@media(max-width:900px){.project-overview-section{padding-top:104px;padding-bottom:28px!important}.project-container{margin-bottom:20px!important}.vfx::after{width:52px;height:52px;margin:-26px 0 0 -26px;background-size:21px}.cs-next-grid{grid-template-columns:1fr}.cs-pc{min-height:240px}.cs-pc h3{font-size:21px!important}.cs-pc p{font-size:13.5px!important}} 278@media(prefers-reduced-motion:reduce){.vfx::after{transition:none!important}.cs-pc{transition:none!important}} 279</style>
280<script> 281document.addEventListener('DOMContentLoaded',function(){ 282function fixSizes(){Array.prototype.forEach.call(document.querySelectorAll('.project-overview-section img[srcset]'),function(img){var w=img.getBoundingClientRect().width;if(w>40){img.sizes=Math.ceil(w*1.4/window.innerWidth*100)+'vw'}})} 283fixSizes(); 284var szT;window.addEventListener('resize',function(){clearTimeout(szT);szT=setTimeout(fixSizes,250)},{passive:true}); 285Array.prototype.forEach.call(document.querySelectorAll('.w-embed-youtubevideo'),function(w){ 286var f=w.querySelector('iframe');if(!f)return; 287var src=f.getAttribute('src')||'';var m=src.match(/embed\/([\w-]+)/);if(!m)return; 288var ttl=f.getAttribute('title')||'video'; 289var d=document.createElement('div');d.className='vfx';d.setAttribute('role','button');d.tabIndex=0;d.setAttribute('aria-label','Play: '+ttl); 290d.style.backgroundImage="url('https://img.youtube.com/vi/"+m[1]+"/maxresdefault.jpg')"; 291var img=new Image();img.onload=function(){if(img.naturalWidth<200){d.style.backgroundImage="url('https://img.youtube.com/vi/"+m[1]+"/hqdefault.jpg')"}};img.src='https://img.youtube.com/vi/'+m[1]+'/maxresdefault.jpg'; 292function play(){var n=document.createElement('iframe');n.src=src+(src.indexOf('?')>-1?'&':'?')+'autoplay=1';n.title=ttl;n.setAttribute('allow','autoplay; encrypted-media');n.setAttribute('allowfullscreen','');n.style.cssText='position:absolute;left:0;top:0;width:100%;height:100%;border:0';w.appendChild(n);d.remove()} 293d.addEventListener('click',play); 294d.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();play()}}); 295f.remove();w.appendChild(d);w.classList.add('vframe'); 296}); 297window.addEventListener('load',function(){ 298fixSizes(); 299Array.prototype.forEach.call(document.querySelectorAll('.w-embed-youtubevideo.vframe'),function(w){if(w.getBoundingClientRect().width<60){w.classList.remove('vframe');w.style.display='none'}}); 300}); 301}); 302</script>
302</div><div class="w-embed w-script"><style>:root{--acc:#cbff83} 303.project-overview-section{background-image:url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a73c5627880653113e21f5d_bg-jlam.jpg')!important} 304a.outline-button{font-family:Poppins,sans-serif!important;font-weight:600!important;font-size:16px!important;color:#fff!important} 305a.outline-button:hover{color:#111!important} 306#process-board{margin-top:3em!important} 307#process-board,#msg-analysis,#cardsort-template,#eco-map{border-radius:1.6em;background:#181818;padding:2.2em 2.4em} 308@media(max-width:900px){#process-board,#msg-analysis,#cardsort-template,#eco-map{padding:1em 1.1em}} 309.jt-card,.ju-card,.jv-card,.jc-card,.jd-card{border:none!important} 310#poster-btn{display:flex!important;width:fit-content!important;max-width:max-content!important;margin-left:auto!important;margin-right:auto!important} 311.ax-pre{opacity:0;transform:translate3d(0,60px,0);transition:opacity .7s cubic-bezier(.25,.46,.45,.94),transform .7s cubic-bezier(.25,.46,.45,.94);will-change:opacity,transform} 312.ax-pre.ax-in{opacity:1;transform:translate3d(0,0,0)} 313@media (prefers-reduced-motion:reduce){.ax-pre{opacity:1;transform:none;transition:none}} 314</style>
315<script> 316(function(){ 317function init(){ 318var els=[]; 319['#mealcoach-widget','.jt-card','.ju-card','.jv-card','.jc-card','.jd-card','.jp-row','#process-board','#msg-analysis','#cardsort-template','#eco-map'].forEach(function(s){ 320document.querySelectorAll(s).forEach(function(e){e.classList.add('ax-pre');els.push(e);}); 321}); 322function tick(){ 323var wh=window.innerHeight||document.documentElement.clientHeight; 324var trig=wh*0.9; 325els.forEach(function(e){ 326var r=e.getBoundingClientRect(); 327if(r.top<trig){e.classList.add('ax-in');} 328else if(r.top>=wh){e.classList.remove('ax-in');} 329}); 330} 331tick(); 332setInterval(tick,60); 333} 334if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();} 335})(); 336</script>
336</div><section class="project-overview-section"><div class="w-layout-blockcontainer project-container w-container"><h1 data-w-id="c4d24880-b442-1caa-2e43-0f4bb70a0eda" style="-webkit-transform:translate3d(-5em, 0, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-moz-transform:translate3d(-5em, 0, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-ms-transform:translate3d(-5em, 0, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);transform:translate3d(-5em, 0, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);opacity:0" class="project-heading">AI as a Tool, Not a Feature: an ethical addition to JLAM's dietary support</h1><a href="#" data-w-id="211a8835-6d87-bcaf-f3cf-66f62ebea7c2" style="-webkit-transform:translate3d(0, 3em, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-moz-transform:translate3d(0, 3em, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);-ms-transform:translate3d(0, 3em, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);transform:translate3d(0, 3em, 0) scale3d(1, 1, 1) rotateX(0) rotateY(0) rotateZ(0) skew(0, 0);opacity:0" class="project-main-image-container w-inline-block w-lightbox"><img sizes="100px" loading="lazy" width="100" src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2.jpg" alt="A dark food-photography scene of a steak salad next to two app screens scanning and logging that same meal with AI" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7483b0743c21fc212a9ab0_jlam-hero-scan-v2.jpg 2048w" class="project-main-image taalcafe"/>
336<script type="application/json" class="w-json">{ 337 "items": [], 338 "group": "Project Images" 339}</script>
339</a><div class="project-details"><div id="w-node-dec11eef-aca3-49fa-85b1-9a920692dc97-e0eb22fe" data-w-id="dec11eef-aca3-49fa-85b1-9a920692dc97" class="mini-details"><div class="item"><h1 class="project-details-approach"><strong class="bold-text">JLAM</strong></h1><p class="mini-paragraph">(Je Leefstijl Als Medicijn) is a Dutch community that helps people prevent and reverse lifestyle-related illness by treating food and daily habits as medicine</p></div><div class="item"><h1 class="project-details-approach"><strong class="bold-text-2">Headquarters</strong></h1><p class="mini-paragraph">Netherlands</p></div><div class="item"><h1 class="project-details-approach"><strong class="bold-text-4">Industry</strong></h1><p class="mini-paragraph">Health & lifestyle</p></div><div class="item"><h1 class="project-details-approach"><strong class="bold-text-3">Services</strong></h1><p class="mini-paragraph">- UX research<span><br/>- AI ethics & privacy<br/>- App design<br/>- Prototyping<br/>- User testing</span></p></div></div><div id="w-node-_4932abb9-7d32-56fb-c778-904d6850d081-e0eb22fe" data-w-id="4932abb9-7d32-56fb-c778-904d6850d081" class="client-explainer"><div class="item client"><h1 class="client">The challenge</h1><p class="paragraph-5">People with diabetes don't lack dietary information - they struggle to judge its credibility. Advice from clinicians, guidelines, communities, influencers and AI often contradicts itself, and JLAM's own community app raised real ethical concerns: sensitive health data posted publicly, friend-matching without consent, and AI assistants that turned out to be ChatGPT with a persona layer.</p></div><div class="item client"><h1 class="client">Solution</h1><p class="paragraph-5">A redesign of JLAM's app where AI is a tool, not a feature: a MealCoach with four ways to log a meal (AI being just one, clearly-labelled option), health data owned by the user instead of the forum, and continuous AI disclosure. Validated over three testing rounds, ending at 80-93% feature success and an overall app score of 4.40/5.</p></div><a href="https://www.figma.com/proto/5U7QQ9sNepa1DtOKmg8pVh/JLAM?node-id=2995-7225&t=kxmWDqkzm9y1j1vf-1" data-w-id="02c5af0e-7768-1787-35c5-c7b0edee3a7a" class="outline-button item w-inline-block"><div class="button-text-slide-container"><div id="w-node-_02c5af0e-7768-1787-35c5-c7b0edee3a7c-e0eb22fe" class="button-text item">View Final Prototype</div><div id="w-node-_02c5af0e-7768-1787-35c5-c7b0edee3a7e-e0eb22fe" class="button-text dark-text items">View Final Prototype</div></div><div data-w-id="02c5af0e-7768-1787-35c5-c7b0edee3a80" class="hover-background"></div></a><a href="https://www.figma.com/design/wtbnU92E87WyjLWYkNMz4l/Graduation-poster?node-id=0-1&t=OvqTKW2Zl0N4oUsh-1" data-w-id="ee487309-6443-c2f8-4498-e26ee7f7252f" class="outline-button item w-inline-block">Check out poster presentation</a></div></div><div class="w-embed w-script"><style>.jlv2{position:relative;border-radius:2.1em;overflow:hidden;border:2px solid var(--acc,#cbff83);margin:2.5em 0;aspect-ratio:16/9;cursor:pointer}.jlv2 .fx{position:absolute;inset:0;background-size:cover;background-position:center;background-color:#0c0c0c}.jlv2 .fx::before{content:'';position:absolute;inset:0;background:linear-gradient(45deg,transparent 44%,var(--acc,#cbff83)),linear-gradient(180deg,rgba(0,0,0,.28),rgba(0,0,0,.4));opacity:.92}.jlv2 .fx::after{content:'';position:absolute;left:50%;top:50%;width:64px;height:64px;margin:-32px 0 0 -32px;border-radius:50%;background-color:var(--acc,#cbff83);opacity:.96;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M9.5 6.5l9 5.5-9 5.5z' fill='%23111'/></svg>");background-repeat:no-repeat;background-position:center;background-size:26px;transition:transform .2s}.jlv2:hover .fx::after{transform:scale(1.08)}.jlv2 iframe{position:absolute;inset:0;width:100%;height:100%;border:0}</style><div class="jlv2" id="jlv1" role="button" tabindex="0" aria-label="Play the JLAM explainer video"><div class="fx" style="background-image:url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7450818bf6d539cdfaf185_jlam-video-thumb.jpg')"></div></div>
339<script>(function(){var v=document.getElementById('jlv1');function go(){v.innerHTML='<iframe src="https://www.youtube.com/embed/QZ55pHMrNEo?autoplay=1" allow="autoplay; encrypted-media; fullscreen" allowfullscreen title="JLAM explainer video"></iframe>'}v.addEventListener('click',go,{once:true});v.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();go()}},{once:true})})()</script>
339</div><div class="text-section afterone"><h1 class="client headersection">Try MealCoach AI yourself</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">This is the app's meal-logging agent, rebuilt to run right here on the page. Upload a photo of a meal or scan a barcode: AI vision estimates the ingredients and macros, you review and correct anything it got wrong, and the coach explains what the numbers mean for a diabetic diet. AI as a tool, with you in control - and below the demo, the same flow as it looks in the V3 prototype.</p></div><div class="w-embed"><style> 340@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@700;800&display=swap"); 341/* MealCoach AI widget â styles */ 342#ma-root{--ma-bg:#171717;--ma-panel:#1c1c1c;--ma-txt:#f2f2f2;--ma-dim:#e6e6e6;--ma-grey:#adadad;--ma-line:#8f8f8f;--ma-dark:#1d1d1d; 343font-family:'Poppins',sans-serif;width:100%;max-width:100%;margin:0 auto;} 344:where(#ma-root,#ma-root *){box-sizing:border-box;margin:0;padding:0;} 345#ma-stage{position:relative;width:100%;aspect-ratio:1316/687;background:var(--ma-bg);overflow:hidden;border-radius:2.1rem;} 346/* right photo half */ 347#ma-photo{position:absolute;top:0;right:0;width:46%;height:100%;background-size:cover;background-position:center;} 348#ma-photo::before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,var(--ma-bg) 0%,rgba(23,23,23,.55) 12%,rgba(23,23,23,0) 38%);} 349/* hero (frames 1-2) */ 350#ma-hero{position:absolute;top:0;right:0;width:52%;height:100%;pointer-events:none;} 351#ma-hero img{position:absolute;} 352#ma-hero .ma-tacos{right:10%;top:2%;height:92%;} 353#ma-hero .ma-cola{right:-4%;top:26%;width:52%;transform:rotate(-24deg);} 354/* left panel */ 355#ma-panel{position:absolute;inset:0;width:57%;padding:6.5% 3% 5.5% 6.8%;display:flex;flex-direction:column;z-index:3;} 356.ma-h{color:#fff;font-weight:800;font-size:clamp(22px,3.4vw,46px);line-height:1.18;letter-spacing:.2px;margin-bottom:.55em;} 357.ma-p{color:var(--ma-dim);font-weight:400;font-size:clamp(13px,1.55vw,21.5px);line-height:1.5;max-width:30em;} 358.ma-p b,.ma-p strong{color:#fff;font-weight:700;} 359.ma-name{color:#fff;font-weight:700;font-size:clamp(17px,2.2vw,30px);margin:.35em 0 .8em;} 360.ma-label{color:var(--ma-dim);font-weight:400;font-size:clamp(13px,1.55vw,21.5px);margin-bottom:.6em;} 361/* metric list */ 362.ma-list{list-style:none;color:var(--ma-dim);font-size:clamp(13px,1.55vw,21.5px);line-height:1.62;} 363.ma-list b{color:#fff;font-weight:700;} 364/* buttons */ 365.ma-btnrow{margin-top:auto;display:flex;gap:4.5%;flex-wrap:wrap;row-gap:10px;} 366.ma-btn{display:inline-block;background:var(--ma-grey);color:var(--ma-dark);border:none;cursor:pointer;border-radius:10px; 367font-family:inherit;font-weight:700;font-size:clamp(13px,1.6vw,22px);padding:16px 32px;transition:background .18s,transform .18s;} 368.ma-btn:hover{background:#fff;transform:translateY(-2px);} 369.ma-btn:disabled{opacity:.45;cursor:default;transform:none;} 370/* input pill */ 371.ma-pill{display:flex;align-items:center;gap:.7em;border:3px solid var(--ma-line);border-radius:16px;padding:16px 32px;max-width:26em;background:transparent;} 372.ma-pill svg{width:1.15em;height:1.15em;flex:none;font-size:clamp(14px,1.7vw,24px);} 373.ma-pill input{flex:1;min-width:0;background:transparent;border:none;outline:none;color:#fff;font-family:inherit;font-weight:700;font-size:clamp(16px,2vw,28px);} 374.ma-pill input::placeholder{color:#777;} 375.ma-pill .ma-unit{color:#9a9a9a;font-weight:700;font-size:clamp(14px,1.7vw,24px);white-space:nowrap;} 376.ma-pill input[type=number]::-webkit-outer-spin-button,.ma-pill input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;} 377.ma-pill input[type=number]{-moz-appearance:textfield;appearance:textfield;} 378/* upload icon + zone */ 379#ma-upicon{position:absolute;left:-6%;bottom:-14%;width:46%;z-index:1;color:#3a3a3a;pointer-events:none;} 380#ma-drop{position:absolute;inset:0;cursor:pointer;z-index:2;} 381#ma-drop.ma-over{outline:3px dashed #666;outline-offset:-14px;} 382/* progress (frame 2) */ 383.ma-prog{margin-top:auto;max-width:26em;} 384.ma-prog-row{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:14px;} 385.ma-prog-row b{color:#fff;font-weight:700;font-size:clamp(14px,1.8vw,24px);} 386.ma-track{height:.5em;background:#4d4d4d;border-radius:99px;overflow:hidden;font-size:clamp(13px,1.55vw,21px);} 387.ma-fill{height:100%;width:0%;background:#fff;border-radius:99px;transition:width .35s ease;} 388/* views */ 389.ma-view{display:none;flex:1;flex-direction:column;min-height:0;} 390.ma-view.ma-on{display:flex;} 391.ma-note{color:#9dd39d;font-size:clamp(12px,1.35vw,18px);margin-top:.9em;line-height:1.45;} 392.ma-err{color:#e89b9b;} 393.ma-scroll{overflow-y:auto;scrollbar-width:thin;scrollbar-color:#555 transparent;padding-right:.5em;} 394/* disclosure */ 395#ma-ai-tag{position:absolute;right:12px;bottom:10px;z-index:4;color:#8d8d8d;font-size:11px;letter-spacing:.4px;background:rgba(20,20,20,.55);padding:3px 9px;border-radius:99px;} 396/* mobile */ 397@media (max-width:767px){ 398#ma-stage{aspect-ratio:auto;display:flex;flex-direction:column;min-height:540px;border-radius:1.4rem;} 399#ma-photo{position:relative;width:100%;height:215px;flex:none;order:0;} 400#ma-photo::before{background:linear-gradient(0deg,var(--ma-bg) 2%,rgba(23,23,23,0) 45%);} 401#ma-panel{position:relative;width:100%;flex:1;padding:26px 20px 30px;order:1;} 402#ma-hero{position:relative;width:100%;height:250px;flex:none;order:2;overflow:hidden;} 403#ma-hero .ma-tacos{right:14%;top:0;height:125%;} 404#ma-hero .ma-cola{right:-6%;bottom:6%;width:56%;transform:rotate(-24deg);} 405#ma-upicon{display:none;} 406.ma-h{font-size:26px;} 407.ma-p,.ma-label,.ma-list{font-size:15px;} 408.ma-name{font-size:20px;} 409.ma-btn{font-size:15px;} 410.ma-btnrow{margin-top:28px;} 411.ma-prog{margin-top:28px;} 412.ma-prog-row b{font-size:16px;} 413.ma-pill input{font-size:18px;}
414.ma-pill svg,.ma-pill .ma-unit{font-size:16px;} 415.ma-hero-hide{display:none;} 416} 417</style></div><div class="w-embed"><style>/* meal agent: the totals list must start flush with the meal name. Webflow's normalize sets ul{padding-left:40px} and the widget's own reset uses :where(), which has zero specificity and loses to it - hence the 40px indent. */ 418#ma-root .ma-list{padding-left:0!important;margin-left:0!important;list-style:none!important} 419/* label + value read as two aligned columns on every screen (values used to be flung to the right edge on desktop and run inline on phones) */ 420@supports (display:contents){ 421#ma-root .ma-list{display:grid!important;grid-template-columns:max-content max-content!important;column-gap:1.15em!important;row-gap:0!important;align-content:start!important;justify-content:start!important} 422#ma-root .ma-list li{display:contents!important} 423#ma-root .ma-list li b{white-space:nowrap!important;text-align:left!important} 424} 425@supports not (display:contents){#ma-root .ma-list li{display:block!important}#ma-root .ma-list li b{text-align:left!important;white-space:nowrap!important}} 426/* desktop: hold the dynamic text back from the photo fade on the right of the stage */ 427@media(min-width:768px){#ma-root #ma-panel{padding-right:6%!important}#ma-root #ma-tot-h{font-size:clamp(20px,2.9vw,36px)!important}} 428@media(min-width:1100px){#ma-root #ma-panel{padding-right:8%!important}} 429/* px, not em: the template root font is vw-scaled so em padding collapses on phones */ 430@media(max-width:900px){.jt-card,.ju-card,.jv-card,.jc-card,.jd-card{padding:26px 22px!important}} 431@media(max-width:479px){.jt-card,.ju-card,.jv-card,.jc-card,.jd-card{padding:24px 18px!important}} 432/* round 2 chart: 2 columns x 3 rows on phones. !important because the chart embed's own .ju-g{flex:1} loads later */ 433@media(max-width:767px){ 434.ju-groups{flex-wrap:wrap!important;justify-content:center!important;align-items:flex-end!important;gap:34px 18px!important} 435.ju-g{flex:0 0 calc(50% - 9px)!important;max-width:calc(50% - 9px)!important} 436.ju-bars{height:168px!important;gap:9px!important} 437.ju-b{width:24px!important} 438.ju-b i{font-size:10.5px!important;top:-1.45em!important} 439.ju-gl{font-size:12px!important} 440.ju-legend{gap:.8em 1.2em!important;margin-bottom:1.9em!important} 441} 442/* poster button: room above, and clear air before the poster photo below */ 443#poster-btn{margin-top:30px!important;margin-bottom:52px!important} 444@media(max-width:767px){#poster-btn{margin-top:26px!important;margin-bottom:60px!important;padding-left:26px!important;padding-right:26px!important;max-width:100%!important}} 445/* meal agent buttons: site outline style, both on one row, primary action filled and on the right */ 446#ma-root .ma-btnrow{display:flex!important;flex-wrap:nowrap!important;gap:12px!important;align-items:stretch!important} 447#ma-root .ma-btnrow .ma-btn{flex:1 1 0!important;min-width:0!important;white-space:nowrap!important;text-align:center!important;padding:15px 16px!important} 448#ma-root .ma-btnrow .ma-btn:only-child{flex:0 1 auto!important;padding:15px 34px!important} 449#ma-root .ma-btn{background:transparent!important;color:#fff!important;border:2.5px solid #fff!important;border-radius:2em!important;font-weight:600!important;transition:background .2s,color .2s,transform .2s!important} 450#ma-root .ma-btn:hover:not(:disabled){background:#fff!important;color:#111!important;transform:translateY(-2px)!important} 451#ma-root .ma-btn:disabled{opacity:.45!important;transform:none!important} 452#ma-root #ma-item-rm,#ma-root #ma-ask-skip{order:1!important} 453#ma-root #ma-item-go,#ma-root #ma-ask-add{order:2!important} 454#ma-root #ma-sum-go,#ma-root #ma-item-go,#ma-root #ma-ask-add,#ma-root #ma-tot-go,#ma-root #ma-adv-go,#ma-root #ma-err-go{background:#fff!important;color:#111!important;border-color:#fff!important} 455#ma-root #ma-sum-go:hover:not(:disabled),#ma-root #ma-item-go:hover:not(:disabled),#ma-root #ma-ask-add:hover:not(:disabled),#ma-root #ma-tot-go:hover:not(:disabled),#ma-root #ma-adv-go:hover:not(:disabled),#ma-root #ma-err-go:hover:not(:disabled){background:#e9e9e9!important;color:#111!important} 456@media(max-width:479px){#ma-root .ma-btnrow .ma-btn{font-size:14px!important;padding:14px 10px!important}} 457/* keep the estimates disclaimer clear of the buttons */ 458@media(max-width:767px){#ma-panel{padding-bottom:62px!important}#ma-ai-tag{bottom:14px!important;right:14px!important}} 459#ma-root .ma-btnrow{margin-bottom:6px!important}
460/* phones: stop slide-ins starting off-screen - it reads as missing padding */ 461@media(max-width:767px){ 462.project-overview-section .client, 463.project-overview-section .client.headersection, 464.project-overview-section .paragraph-5, 465.project-overview-section .paragraph-5.headersection, 466.project-overview-section .outline-button, 467.project-overview-section .client-explainer, 468.project-overview-section .mini-details, 469.project-overview-section .project-heading{transform:none!important;opacity:1!important} 470}</style></div><div id="mealcoach-widget" class="w-embed"><div id="ma-root"> 471<div id="ma-stage"> 472 <div id="ma-hero"> 473 <img class="ma-tacos" src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a63cc3f47f9a2544b8f543f_meal-agent-tacos.webp" alt=""> 474 <img class="ma-cola" src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a63cc3fcd8a6977c93ec7d5_meal-agent-cola.webp" alt=""> 475 </div> 476 <div id="ma-photo" style="display:none"></div> 477 <svg id="ma-upicon" viewBox="0 0 2000 2000" fill="currentColor" aria-hidden="true"> 478 <path d="M1955.757,1197.324v78.414c-9.765,40.232-16.064,81.734-29.931,120.502c-62.35,174.649-233.581,301.499-418.729,298.715c-118.158-1.757-236.413-0.342-354.62-0.342h-23.632v-414.432h108.49c24.12,0,49.9,4.004,63.62-23.534c13.232-26.561-4.394-44.822-18.163-63.815c-80.416-111.42-160.978-222.743-241.54-334.114c-27.196-37.596-55.807-37.596-82.955-0.049c-82.71,114.349-165.372,228.797-248.034,343.195c-3.271,4.541-6.64,9.033-9.374,13.867c-17.138,30.028,0.342,62.985,35.008,64.01c37.888,1.172,75.875,0.391,113.813,0.439h21.239v414.432h-21.874c-135.589,0-271.227-0.683-406.864,0.196c-134.661,0.879-238.123-58.542-316.048-164.348c-65.329-88.667-93.16-189.395-77.779-299.154C63.811,1121.4,117.91,1033.27,207.993,967.794c5.029-3.662,10.009-7.47,15.526-10.351c10.351-5.419,12.842-11.718,9.277-23.925c-52.439-179.532,86.08-352.668,272.447-340.266c23.339,1.562,46.336,7.958,70.407,12.304c16.014-25.682,31.004-54.44,50.339-79.83c129.388-169.864,300.62-245.154,512.426-213.759c223.475,33.104,396.854,202.675,437.478,424.148c6.592,35.838,18.993,48.289,53.171,59.274c186.66,60.104,290.757,192.422,322.786,384.014C1952.876,1185.411,1954.438,1191.367,1955.757,1197.324z"/> 479 </svg> 480 <input type="file" id="ma-file" accept="image/*" multiple style="display:none"> 481 <div id="ma-panel"> 482 483 <div class="ma-view ma-on" id="ma-v-upload"> 484 <h3 class="ma-h">Upload a photo<br>of your meal<br>or barcode</h3> 485 <p class="ma-p" id="ma-up-hint">Click anywhere or drop an image here. You can select several photos at once.</p> 486 <div id="ma-drop" role="button" aria-label="Upload a photo of your meal or barcode" tabindex="0"></div> 487 </div> 488 489 <div class="ma-view" id="ma-v-proc"> 490 <h3 class="ma-h">Upload a photo<br>of your meal<br>or package</h3> 491 <div class="ma-prog"> 492 <div class="ma-prog-row"><b id="ma-prog-label">Reading images...</b><b id="ma-prog-pct">0%</b></div> 493 <div class="ma-track"><div class="ma-fill" id="ma-fill"></div></div> 494 </div> 495 </div> 496 497 <div class="ma-view" id="ma-v-sum"> 498 <h3 class="ma-h" id="ma-sum-h">AI Vision Analysis</h3> 499 <p class="ma-p" id="ma-sum-p"></p> 500 <div class="ma-btnrow"><button class="ma-btn" id="ma-sum-go">Continue</button></div> 501 </div> 502 503 <div class="ma-view" id="ma-v-item"> 504 <h3 class="ma-h" id="ma-item-h">AI Vision Analysis</h3> 505 <p class="ma-label">One of the observed<br>ingredients from your meal is:</p> 506 <div class="ma-name" id="ma-item-name"></div> 507 <p class="ma-label">I’m observing about:</p> 508 <div class="ma-pill"> 509 <svg viewBox="0 0 24 24" fill="none" stroke="#bdbdbd" stroke-width="2"><path d="M17 3l4 4L8 20H4v-4L17 3z"/></svg> 510 <input type="number" id="ma-item-g" min="1" max="5000" inputmode="decimal" aria-label="Portion in grams or millilitres"> 511 <span class="ma-unit">g / ml</span> 512 </div> 513 <div class="ma-btnrow"> 514 <button class="ma-btn" id="ma-item-go">Continue</button> 515 <button class="ma-btn" id="ma-item-rm">Remove</button> 516 </div> 517 </div> 518 519 <div class="ma-view" id="ma-v-ask"> 520 <h3 class="ma-h">AI Vision Analysis</h3> 521 <p class="ma-label" id="ma-ask-q">Are there any ingredients I<br>didn’t observe?</p> 522 <div class="ma-pill" id="ma-ask-pill"> 523 <svg viewBox="0 0 24 24" fill="none" stroke="#bdbdbd" stroke-width="2"><path d="M17 3l4 4L8 20H4v-4L17 3z"/></svg> 524 <input type="text" id="ma-ask-name" placeholder="Beetroot" aria-label="Missing ingredient name">
525 <span class="ma-unit" id="ma-ask-unit" style="display:none">g / ml</span> 526 </div> 527 <p class="ma-note" id="ma-ask-note" style="display:none"></p> 528 <div class="ma-btnrow"> 529 <button class="ma-btn" id="ma-ask-skip">Skip</button> 530 <button class="ma-btn" id="ma-ask-add">Add ingredient</button> 531 </div> 532 </div> 533 534 <div class="ma-view" id="ma-v-tot"> 535 <h3 class="ma-h" id="ma-tot-h"></h3> 536 <ul class="ma-list ma-scroll" id="ma-tot-list"></ul> 537 <div class="ma-btnrow"><button class="ma-btn" id="ma-tot-go">Read diabetic guidance</button></div> 538 </div> 539 540 <div class="ma-view" id="ma-v-adv"> 541 <h3 class="ma-h">Diabetic<br>Guidance</h3> 542 <p class="ma-p ma-scroll" id="ma-adv-p"></p> 543 <div class="ma-btnrow"><button class="ma-btn" id="ma-adv-go">Scan another meal</button></div> 544 </div> 545 546 <div class="ma-view" id="ma-v-err"> 547 <h3 class="ma-h">Hmm, that<br>didn’t work</h3> 548 <p class="ma-p ma-err" id="ma-err-p"></p> 549 <div class="ma-btnrow"><button class="ma-btn" id="ma-err-go">Try again</button></div> 550 </div> 551 552 </div> 553 <span id="ma-ai-tag">MealCoach AI — estimates, not medical advice</span> 554</div> 555</div></div><div class="w-embed w-script">
555<script> 556window.MA = (function () { 557var C = { 558url: "https://claude.armandasa.workers.dev", 559model: "claude-haiku-4-5-20251001", 560maxDim: 1280, 561zxing: "https://cdn.jsdelivr.net/npm/@zxing/[email protected]/umd/index.min.js", 562offProduct: "https://world.openfoodfacts.org/api/v2/product/", 563defaultServing: 100 564}; 565 566var VISION_SYS = 'You are MealCoach, a vision-based nutrition analysis assistant.\n' + 567'You receive a meal photo and return STRICT JSON (no prose, no markdown fences):\n' + 568'{"meal_name":"<brief meal name>","observed_items":[{"name":"<food item>","estimated_grams":<number>,' + 569'"data_source":"label"|"brand_knowledge"|"estimate","brand_or_product":"<product name>"|null,' + 570'"nutrition_per_100g":{"calories":<kcal>,"protein":<g>,"carbs":<g>,"fat":<g>,"fiber":<g>,"sugar":<g>}}],' + 571'"total_estimated_grams":<number>,"confirmation_message":"<one friendly sentence describing what you see>"}\n' + 572'HOW TO POPULATE nutrition_per_100g (all values PER 100 GRAMS):\n' + 573'1. label visible: transcribe a readable nutrition panel, convert to per 100 g, data_source "label".\n' + 574'2. branded product recognized: use that knowledge, data_source "brand_knowledge".\n' + 575'3. generic food: estimate typical values, data_source "estimate".\n' + 576'Units per 100 g: calories kcal; protein, carbs, fat, fiber, sugar in grams. Use 0 for unknowns. ' + 577'Prefer conservative estimates. Always give non-zero estimated_grams for visible food. Output ONLY the JSON object.'; 578 579var ING_SYS = 'You estimate typical nutrition for a single food ingredient. Return STRICT JSON only, values PER 100 GRAMS: ' + 580'{"calories":<kcal>,"protein":<g>,"carbs":<g>,"fat":<g>,"fiber":<g>,"sugar":<g>}. Use 0 for unknowns. Conservative, typical values.'; 581 582var DIAB_SYS = 'You are a concise nutrition coach for people managing blood sugar. Given a meal\'s nutrition totals, ' + 583'give 2-3 short sentences of practical guidance focused on: likely glycemic impact (carbs, sugar, fiber ratios); ' + 584'overall macro balance (protein / fat / fiber); one constructive suggestion. Keep it warm, non-alarmist. ' + 585'Do not give medical advice. Output plain text only.'; 586 587var KEYS = ["calories", "protein", "carbs", "fat", "fiber", "sugar"]; 588var LBL = { calories: "Calories", protein: "Protein", carbs: "Carbs", fat: "Fat", fiber: "Fibre", sugar: "Sugar" }; 589 590function num(x) { var v = parseFloat(x); return isFinite(v) ? v : 0; } 591 592function chat(messages, jsonMode, temp, attempt) { 593var sys = "", ms = []; 594messages.forEach(function (m) { 595if (m.role === "system") { sys += (sys ? "\n" : "") + m.content; } else { ms.push(m); } 596}); 597var body = { mode: "meal", model: C.model, max_tokens: 1200, temperature: temp, messages: ms }; 598if (sys) body.system = sys; 599return fetch(C.url, { 600method: "POST", 601headers: { "Content-Type": "application/json" }, 602body: JSON.stringify(body) 603}).then(function (r) { 604if (r.status === 429) { var e429 = new Error("The AI is catching its breath â please wait a minute and try again."); e429.is429 = true; throw e429; } 605if (!r.ok) return r.text().then(function (t) { throw new Error("AI request failed (" + r.status + "). " + t.slice(0, 140)); }); 606return r.json(); 607}).then(function (d) { 608var c = ""; 609if (d && d.content && d.content[0] && d.content[0].text) { c = d.content[0].text; } 610else if (d && d.choices && d.choices[0] && d.choices[0].message) { c = d.choices[0].message.content || ""; } 611else if (d && typeof d.text === "string") { c = d.text; } 612c = c.trim(); 613if (!c) throw new Error("Empty AI response"); 614return c; 615}).catch(function (e) { 616if (!attempt && !e.is429) return chat(messages, jsonMode, temp, 1); 617if (!attempt && e.is429) return new Promise(function (res) { setTimeout(res, 20000); }).then(function () { return chat(messages, jsonMode, temp, 1); }); 618throw e; 619}); 620} 621 622function jsonChat(messages, temp, attempt) { 623return chat(messages, true, temp).then(parseJson).catch(function (e) { 624if (!attempt) return jsonChat(messages, temp, 1); 625throw e; 626}); 627} 628 629function parseJson(raw) { 630var t = (raw || "").trim().replace(/^```(?:json)?\s*/, "").replace(/\s*```$/, ""); 631try { return JSON.parse(t); } catch (e) { 632var m = t.match(/\{[\s\S]*\}/); if (m) return JSON.parse(m[0]); throw e; 633} 634} 635 636function fileToDataUrl(file) { 637return new Promise(function (res, rej) { 638var img = new Image(), u = URL.createObjectURL(file); 639img.onload = function () {
640var w = img.naturalWidth, h = img.naturalHeight, s = Math.min(1, C.maxDim / Math.max(w, h)); 641var cv = document.createElement("canvas"); cv.width = Math.round(w * s); cv.height = Math.round(h * s); 642cv.getContext("2d").drawImage(img, 0, 0, cv.width, cv.height); 643URL.revokeObjectURL(u); res(cv.toDataURL("image/jpeg", 0.88)); 644}; 645img.onerror = function () { URL.revokeObjectURL(u); rej(new Error("Could not read that image file.")); }; 646img.src = u; 647}); 648} 649 650var zxingP = null; 651function loadZxing() { 652if (zxingP) return zxingP; 653zxingP = new Promise(function (res) { 654var s = document.createElement("script"); s.src = C.zxing; 655s.onload = function () { res(true); }; s.onerror = function () { res(false); }; 656document.head.appendChild(s); 657}); 658return zxingP; 659} 660 661function decodeBarcode(dataUrl) { 662return loadZxing().then(function (ok) { 663if (!ok || !window.ZXing) return []; 664var reader = new ZXing.BrowserMultiFormatReader(); 665return reader.decodeFromImageUrl(dataUrl).then(function (r) { 666var c = (r.getText() || "").trim(); 667return (/^\d{8,14}$/.test(c)) ? [c] : []; 668}).catch(function () { return []; }); 669}); 670} 671 672function offMacros(n) { 673n = n || {}; 674var kcal = num(n["energy-kcal_100g"]); 675if (!kcal) { var kj = num(n["energy_100g"]) || num(n["energy-kj_100g"]); if (kj) kcal = kj / 4.184; } 676return { calories: Math.round(kcal * 10) / 10, protein: num(n["proteins_100g"]), carbs: num(n["carbohydrates_100g"]), 677fat: num(n["fat_100g"]), fiber: num(n["fiber_100g"]), sugar: num(n["sugars_100g"]) }; 678} 679 680function offLookup(code) { 681return fetch(C.offProduct + encodeURIComponent(code) + ".json?fields=product_name,generic_name,brands,nutriments,serving_quantity,serving_size") 682.then(function (r) { return r.json(); }) 683.then(function (d) { 684if (!d || d.status !== 1) return null; 685var p = d.product || {}, g = num(p.serving_quantity); 686if (!g) { var m = String(p.serving_size || "").toLowerCase().match(/([0-9]+(?:\.[0-9]+)?)\s*g/); if (m) g = num(m[1]); } 687return { barcode: code, name: p.product_name || p.generic_name || "product", brand: p.brands || "", 688serving: g || null, per100: offMacros(p.nutriments) }; 689}).catch(function () { return null; }); 690} 691 692function analyzeVision(dataUrl) { 693return jsonChat([ 694{ role: "system", content: VISION_SYS }, 695{ role: "user", content: [ 696{ type: "image", source: { type: "base64", media_type: "image/jpeg", data: dataUrl.split(",")[1] } }, 697{ type: "text", text: "Analyze this meal photo." } 698] } 699], 0.2); 700} 701 702function estimateIngredient(name) { 703return jsonChat([ 704{ role: "system", content: ING_SYS }, 705{ role: "user", content: "Ingredient: " + name } 706], 0.2).then(function (d) { 707var o = {}; KEYS.forEach(function (k) { o[k] = num(d[k]); }); return o; 708}); 709} 710 711function guidance(totals) { 712var parts = ["calories", "carbs", "sugar", "fiber", "protein", "fat"].map(function (k) { 713return LBL[k] + ": " + totals[k] + (k === "calories" ? " kcal" : " g"); 714}); 715return chat([ 716{ role: "system", content: DIAB_SYS }, 717{ role: "user", content: "Meal totals: " + parts.join(", ") } 718], false, 0.7); 719} 720 721function totals(items) { 722var t = {}; KEYS.forEach(function (k) { t[k] = 0; }); 723items.forEach(function (it) { 724var f = num(it.estimated_grams) / 100, p = it.nutrition_per_100g || {}; 725KEYS.forEach(function (k) { t[k] += num(p[k]) * f; }); 726}); 727KEYS.forEach(function (k) { t[k] = Math.round(t[k] * 10) / 10; }); 728return t; 729} 730 731return { C: C, KEYS: KEYS, LBL: LBL, num: num, chat: chat, parseJson: parseJson, fileToDataUrl: fileToDataUrl, 732decodeBarcode: decodeBarcode, offLookup: offLookup, analyzeVision: analyzeVision, 733estimateIngredient: estimateIngredient, guidance: guidance, totals: totals }; 734})(); 735</script>
735</div><div class="w-embed w-script">
735<script> 736(function () { 737var MA = window.MA; 738var S = { queue: [], photoUrl: null, mode: "vision", vision: null, items: [], idx: 0, kept: [], addName: "", busy: false }; 739 740function $(id) { return document.getElementById(id); } 741function show(view) { 742["upload", "proc", "sum", "item", "ask", "tot", "adv", "err"].forEach(function (v) { 743$("ma-v-" + v).classList.toggle("ma-on", v === view); 744}); 745var hero = view === "upload" || view === "proc"; 746$("ma-hero").style.display = hero ? "" : "none"; 747$("ma-upicon").style.display = view === "upload" ? "" : "none"; 748$("ma-photo").style.display = hero ? "none" : ""; 749} 750function esc(s) { var d = document.createElement("div"); d.textContent = s == null ? "" : String(s); return d.innerHTML; } 751 752var progT = null; 753function progress(run) { 754clearInterval(progT); 755var fill = $("ma-fill"), pct = $("ma-prog-pct"), v = 0; 756if (!run) return; 757fill.style.width = "0%"; pct.textContent = "0%"; 758progT = setInterval(function () { 759v = Math.min(92, v + (v < 55 ? 6 : v < 80 ? 3 : 1)); 760fill.style.width = v + "%"; pct.textContent = Math.round(v) + "%"; 761}, 420); 762} 763 764function startFiles(files) { 765var list = Array.prototype.slice.call(files).filter(function (f) { return /^image\//.test(f.type); }); 766if (!list.length) return; 767S.queue = S.queue.concat(list); 768nextMeal(); 769} 770 771function nextMeal() { 772if (!S.queue.length) { show("upload"); return; } 773var file = S.queue.shift(); 774S.vision = null; S.items = []; S.kept = []; S.idx = 0; S.mode = "vision"; 775show("proc"); progress(true); 776MA.fileToDataUrl(file).then(function (dataUrl) { 777S.photoUrl = dataUrl; 778$("ma-photo").style.backgroundImage = "url(" + dataUrl + ")"; 779return MA.decodeBarcode(dataUrl).then(function (codes) { 780var offs = []; 781var lookups = codes.map(function (c) { 782return MA.offLookup(c).then(function (p) { if (p) offs.push(p); }); 783}); 784return Promise.all(lookups).then(function () { 785return MA.analyzeVision(dataUrl).then(function (vision) { 786return { vision: vision, offs: offs, codes: codes }; 787}); 788}); 789}); 790}).then(function (r) { 791progress(false); 792var v = r.vision || {}; 793v.observed_items = (v.observed_items || []).map(function (it) { 794it.estimated_grams = Math.max(1, Math.round(MA.num(it.estimated_grams)) || 100); 795it.nutrition_per_100g = it.nutrition_per_100g || {}; 796return it; 797}); 798S.vision = v; 799S.barcodeMiss = r.codes.length > 0 && r.offs.length === 0; 800if (r.offs.length) { 801S.mode = "barcode"; 802var off = r.offs[0]; 803var label = ((off.brand ? off.brand + " " : "") + off.name).trim(); 804S.items = [{ name: off.name, brand_or_product: label, estimated_grams: off.serving || MA.C.defaultServing, 805data_source: "barcode_off", nutrition_per_100g: off.per100 }]; 806v.meal_name = label || v.meal_name || "Scanned product"; 807} else { 808S.items = v.observed_items; 809} 810renderSummary(); 811show("sum"); 812}).catch(fail); 813} 814
815function renderSummary() { 816var total = Math.round(S.items.reduce(function (a, i) { return a + MA.num(i.estimated_grams); }, 0)); 817var h = $("ma-sum-h"), p = $("ma-sum-p"); 818if (S.mode === "barcode") { 819h.textContent = "Barcode Analysis"; 820p.innerHTML = "The barcode appears to be <b>" + esc(S.items[0].brand_or_product) + "</b>. The serving size is about: <b>" + total + " g.</b>"; 821} else { 822h.textContent = "AI Vision Analysis"; 823var msg = (S.vision.confirmation_message || "").trim() || ("I see " + S.items.map(function (i) { return i.name; }).join(", ")); 824if (!/[.!?]$/.test(msg)) msg += "."; 825var miss = S.barcodeMiss ? "I spotted a barcode but couldn’t match it in the food database, so I analysed the photo instead.<br><br>" : ""; 826p.innerHTML = miss + esc(msg) + " Altogether it seems to be about: <b>" + total + " g.</b>"; 827} 828} 829 830function reviewNext() { 831if (S.mode === "barcode") { finishItems(); return; } 832if (S.idx >= S.items.length) { show("ask"); askReset(); return; } 833var it = S.items[S.idx]; 834$("ma-item-name").textContent = it.name; 835$("ma-item-g").value = Math.round(MA.num(it.estimated_grams)) || 100; 836show("item"); 837$("ma-item-g").focus(); 838} 839 840function keepItem() { 841var v = MA.num($("ma-item-g").value); 842if (v <= 0) { $("ma-item-g").value = Math.round(MA.num(S.items[S.idx].estimated_grams)) || 100; return; } 843S.items[S.idx].estimated_grams = v; 844S.kept.push(S.items[S.idx]); 845S.idx++; reviewNext(); 846} 847function dropItem() { S.idx++; reviewNext(); } 848 849function askReset() { 850S.askStep = "name"; S.addName = ""; 851$("ma-ask-q").innerHTML = "Are there any ingredients I<br>didn’t observe?"; 852var inp = $("ma-ask-name"); inp.value = ""; inp.type = "text"; inp.placeholder = "Beetroot"; 853$("ma-ask-unit").style.display = "none"; 854$("ma-ask-add").textContent = "Add ingredient"; 855$("ma-ask-skip").textContent = "Skip"; 856} 857function askAdd() { 858if (S.busy) return; 859var inp = $("ma-ask-name"); 860if (S.askStep === "name") { 861var name = inp.value.trim(); 862if (!name) { inp.focus(); return; } 863S.addName = name; S.askStep = "amount"; 864$("ma-ask-q").innerHTML = "Got it: <b>" + esc(name) + "</b>.<br>How much did you have?"; 865inp.value = ""; inp.type = "number"; inp.placeholder = "150"; 866$("ma-ask-unit").style.display = ""; 867$("ma-ask-add").textContent = "Confirm"; 868$("ma-ask-note").style.display = "none"; 869inp.focus(); 870return; 871} 872var amt = MA.num(inp.value); 873if (amt <= 0) { inp.focus(); return; } 874S.busy = true; $("ma-ask-add").disabled = true; $("ma-ask-skip").disabled = true; 875var addedName = S.addName; 876MA.estimateIngredient(addedName).then(function (per100) { 877S.kept.push({ name: addedName, estimated_grams: amt, data_source: "manual_estimate", brand_or_product: null, nutrition_per_100g: per100 }); 878askReset(); 879var note = $("ma-ask-note"); 880note.innerHTML = "Added " + esc(addedName) + " (" + Math.round(amt) + " g) with AI-estimated typical values. Anything else?"; 881note.style.display = ""; 882}).catch(function () { 883var note = $("ma-ask-note"); 884note.innerHTML = "Sorry — I couldn’t look that one up. Try again?"; 885note.style.display = ""; 886S.askStep = "name"; 887}).then(function () { 888S.busy = false; $("ma-ask-add").disabled = false; $("ma-ask-skip").disabled = false; 889}); 890} 891 892function finishItems() { 893if (S.mode === "barcode") S.kept = S.items.slice(); 894if (!S.kept.length) { fail(new Error("No edible items left in this meal â nothing to analyse.")); return; } 895var t = MA.totals(S.kept); 896S.totalsCache = t; 897var grams = Math.round(S.kept.reduce(function (a, i) { return a + MA.num(i.estimated_grams); }, 0)); 898var name = (S.vision && S.vision.meal_name) || "Your meal"; 899$("ma-tot-h").innerHTML = esc(name); 900var rows = ['<li>Total portion: <b>' + grams + 'g</b></li>']; 901MA.KEYS.forEach(function (k) { 902rows.push("<li>" + MA.LBL[k] + ": <b>" + t[k] + (k === "calories" ? " kcal" : "g") + "</b></li>"); 903}); 904$("ma-tot-list").innerHTML = rows.join(""); 905show("tot"); 906} 907 908function showGuidance() { 909if (S.busy) return; 910S.busy = true; $("ma-tot-go").disabled = true; 911MA.guidance(S.totalsCache).then(function (text) { 912$("ma-adv-p").textContent = text || "No guidance text was returned â try re-running this meal."; 913$("ma-adv-go").textContent = S.queue.length ? "Scan next meal (" + S.queue.length + " left)" : "Scan another meal"; 914show("adv"); 915}).catch(fail).then(function () { S.busy = false; $("ma-tot-go").disabled = false; }); 916} 917 918function fail(e) { 919progress(false); 920$("ma-err-p").textContent = (e && e.message) ? e.message : "Something went wrong."; 921show("err"); 922} 923
924function init() { 925var drop = $("ma-drop"), file = $("ma-file"); 926drop.addEventListener("click", function () { file.click(); }); 927drop.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); file.click(); } }); 928["dragover", "dragenter"].forEach(function (ev) { 929drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.add("ma-over"); }); 930}); 931["dragleave", "drop"].forEach(function (ev) { 932drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.remove("ma-over"); }); 933}); 934drop.addEventListener("drop", function (e) { if (e.dataTransfer && e.dataTransfer.files) startFiles(e.dataTransfer.files); }); 935file.addEventListener("change", function () { startFiles(file.files); file.value = ""; }); 936 937$("ma-sum-go").addEventListener("click", function () { S.idx = 0; S.kept = []; reviewNext(); }); 938$("ma-item-go").addEventListener("click", keepItem); 939$("ma-item-rm").addEventListener("click", dropItem); 940$("ma-item-g").addEventListener("keydown", function (e) { if (e.key === "Enter") keepItem(); }); 941$("ma-ask-skip").addEventListener("click", function () { 942if (S.askStep === "amount") { askReset(); return; } 943finishItems(); 944}); 945$("ma-ask-add").addEventListener("click", askAdd); 946$("ma-ask-name").addEventListener("keydown", function (e) { if (e.key === "Enter") askAdd(); }); 947$("ma-tot-go").addEventListener("click", showGuidance); 948$("ma-adv-go").addEventListener("click", nextMeal); 949$("ma-err-go").addEventListener("click", function () { S.queue = []; show("upload"); }); 950} 951 952if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); 953else init(); 954})(); 955</script>
955</div><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2.jpg" loading="lazy" sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-2600.jpg 2600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2-p-3200.jpg 3200w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a749cbf3c3e0ce747da6752_jlam-design-process-dark-v2.jpg 3312w" alt="Double-diamond design process of the JLAM graduation project, from kick-off and research through ideation to three testing rounds and the stakeholder presentation" id="process-board" class="project-secondary-image"/><div class="text-section afterone"><h1 id="w-node-ca10b1da-a58f-936f-cc47-b0a0db0001f5-e0eb22fe" class="client headersection">Discover</h1><p id="w-node-ca10b1da-a58f-936f-cc47-b0a0db0001f7-e0eb22fe" data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">I involved real users from the very start: four in-depth interviews (a reversed prediabetic, two adults with Type 1 diabetes and a diabetes nurse), netnographic research inside JLAM's community app, a journey map of the diabetic experience from first symptoms to long-term self-management, and a study of the diabetic information and keto landscapes JLAM operates in.<span><br/><br/>The research showed diabetes management as a constant negotiation between medical authority and lived experience: people don't lack dietary information - they struggle to judge its credibility, with Type 1 and Type 2 advice dangerously conflated along the way.<br/><br/>A keyword analysis of 13,333 real user messages sent to JLAM's Lampie AI confirmed the direction: food and nutrition dominated (44%), followed by health metrics and body measurements (33%). Users were already treating the AI as a dietary assistant and self-monitoring tool - without the structure, memory or transparency to support it.<br/><br/>The netnography also surfaced concrete ethical problems: members posting sensitive ZWEM biometrics (weight, waist, glucose, HbA1c) publicly in forum comments, a Buddy AI matching people into friendships without meaningful consent, and three in-app AI 'assistants' that all turned out to be ChatGPT with a persona layer - discoverable only by asking.</span></p></div><div class="text-section afterone"><h1 class="client headersection">What the netnography surfaced</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">Two of the findings that started the project: ZWEM health measurements posted publicly in the forum, and Buddy, an AI agent matching members into friendships without meaningful consent.</p></div><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rounded.jpg" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rounded-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rounded-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rounded-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rou
955nded-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d37f3b03ed411eade2_jlam-evidence-rounded.jpg 1834w" alt="Netnography evidence: a public forum post sharing ZWEM biometrics, and the Buddy AI agent profile that matched members without meaningful consent" class="project-secondary-image"/><div class="w-embed"><style>.jd-card{background:#181818;border:1px solid rgba(203,255,131,.25);border-radius:1.6em;padding:2.4em 2.6em;font-family:'Poppins',sans-serif;color:#fff;margin:1em 0 3em}.jd-tag{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#cbff83}.jd-card h4{font-size:19px;font-weight:600;color:#fff;margin:.45em 0 1.6em;line-height:1.3}.jd-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2em}.jd-item{display:flex;flex-direction:column;align-items:center;gap:1.1em;text-align:center}.jd-donut{position:relative;width:150px;height:150px;border-radius:50%;background:conic-gradient(#cbff83 calc(var(--p)*1%),rgba(255,255,255,.09) 0)}.jd-donut::before{content:'';position:absolute;inset:22px;border-radius:50%;background:#181818}.jd-donut b{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:23px;font-weight:700;color:#fff}.jd-l{font-size:13px;color:#d9d9d9;line-height:1.45;margin:0}.jd-l span{display:block;color:#9a9a9a;font-size:11.5px;margin-top:.25em}@media(max-width:900px){.jd-card{padding:1.8em 1.4em}.jd-grid{grid-template-columns:1fr;gap:1.8em}.jd-donut{width:126px;height:126px}}</style><div class="jd-card"><span class="jd-tag">Lampie AI · 13,333 messages · keyword analysis</span><h4>Share of messages mentioning each topic</h4><div class="jd-grid"><div class="jd-item"><div class="jd-donut" style="--p:44.3"><b>44.3%</b></div><p class="jd-l">Food & nutrition<span>recipes, carbs, products, diets</span></p></div><div class="jd-item"><div class="jd-donut" style="--p:32.9"><b>32.9%</b></div><p class="jd-l">Health metrics (ZWEM)<span>glucose, weight, blood values</span></p></div><div class="jd-item"><div class="jd-donut" style="--p:20.4"><b>20.4%</b></div><p class="jd-l">Friendship & coaching<span>support, exercise, social</span></p></div></div></div></div><div class="text-section afterone"><h1 class="client headersection">The diabetic ecosystem</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">Before defining any problem I mapped every actor around a JLAM member - institutional stakeholders, community actors, tracking tools, data brokers, publications and the food industry - and the social, affective, economic and scientific value each exchange carries. The tensions in this map, like medical authority versus AI guidance and social support versus accuracy, became the ethical guardrails for everything designed after it.</p></div><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5.jpg" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5-p-2600.jpg 2600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74feb5fb0245f00a363b66_jlam-ecosystem-circle-dark-v5.jpg 2807w" alt="Diabetes within the JLAM ecosystem: value flows between people living with diabetes, institutional stakeholders, community actors, data brokers, publications and the food industry" id="eco-map" class="project-secondary-image"/><div class="text-section afterone"><h1 class="client headersection">The patient journey</h1>
955<p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">To understand where digital support actually fits, discovery also mapped the typical diabetes patient journey - from the first health trigger through diagnosis, education and long-term adaptation.</p></div><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3.jpg" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74aa4048fa644b0a3ddb67_jlam-journey-map-dark-v3.jpg 2322w" alt="Typical diabetes patient journey map from the discovery research, from baseline life through diagnosis to adaptation" class="project-secondary-image"/><div class="text-section afterone"><h1 id="w-node-_7e570a6c-30d7-d690-bc20-6b076a963bb3-e0eb22fe" class="client headersection">Define</h1><p id="w-node-_56ec6601-4fe4-00e8-aa87-b890e3840979-e0eb22fe" data-w-id="56ec6601-4fe4-00e8-aa87-b890e3840979" class="paragraph-5 headersection">The insights condensed into five defined problems: datafication risk around ZWEM health data, information asymmetry with AI chatbots, trust that collapses when AI can't be corrected, friend-matching without consent, and under-utilised recipes.<span><br/><br/>A card-sorting session with ten participants - analysed with PCA and k-means clustering - revealed three trust orientations (expert-oriented, community-oriented and mixed/skeptical), which were triangulated into three personas: the <strong>Preventative Optimizer</strong>, the <strong>Adaptive Self-Manager</strong> and the <strong>Community-Oriented Support Seeker</strong>. They describe how people seek, trust and act on dietary information - engagement styles, not diagnoses.<br/><br/>Everything came together in one question: how might we support sustainable dietary change in the JLAM community through ethical, trustworthy AI?</span></p></div><div class="w-embed"><style>.jc-card{background:#181818;border:1px solid rgba(203,255,131,.25);border-radius:1.6em;padding:2.4em 2.6em;font-family:'Poppins',sans-serif;color:#fff;margin:2.5em 0}.jc-tag{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#cbff83}.jc-card h4{font-size:19px;font-weight:600;margin:.45em 0 .2em;line-height:1.3;color:#fff}.jc-sub{font-size:13px;color:#9a9a9a;line-height:1.55;margin:0 0 1.8em}.jc-grid{display:grid;grid-template-columns:1fr 1fr;gap:2.4em}.jc-h{font-size:12px;font-weight:600;color:#d9d9d9;letter-spacing:.06em;text-transform:uppercase;margin:0 0 .9em}.jc-r{display:grid;grid-template-columns:minmax(9em,11em) 1fr 2.4em;gap:.8em;align-items:center;margin-bottom:.55em}.jc-l{font-size:12px;color:#d9d9d9;line-height:1.25}.jc-t{height:12px;border-radius:999px;background:rgba(255,255,255,.08)}.jc-f{height:100%;border-radius:999px;background:#cbff83}.jc-f.dim{background:rgba(203,255,131,.38)}.jc-v{font-size:11.5px;font-weight:600;color:#d9d9d9;text-align:right}.jc-insight{font-size:13px;color:#d9d9d9;line-height:1.6;margin:1.8em 0 0;border-top:1px solid rgba(255,255,255,.09);padding-top:1.5em}.jc-insight b{color:#cbff83;font-weight:600}@media(max-width:900px){.jc-card{padding:1.8em 1.4em}.jc-grid{grid-template-columns:1fr}.jc-r{grid-template-columns:minmax(7em,9em) 1fr 2.4em}}</style><div class="jc-card"><span class="jc-tag">Card sorting · 10 participants · 50 claims · 8 sources</span><h4>Who do people trust — and whose voice do they actually hear?</h4><p class="jc-sub">Participants sorted 50 dietary claims onto the source they thought would say them, then ranked all 8 sources by trust. The boards were transcribed into a dataset and analysed in Python (500 claim mappings, 80 trust rankings, k-means clustering).</p><div class="jc-grid"><div><p class="jc-h">Average trust rank (1–8)</p><div class="jc-r"><span class="jc-l">Medical specialist</span><div class="jc-t"><div class="jc-f" style="width:97.5%"></div></div><span class="jc-v">7.8</span></div><div class="jc-r"><span class="jc-l">Registered dietitian</span><div class="jc-t"><div class="jc-f" style="width:77.5%"></div></div><span class="jc-v">6.2</span></div><div class="jc-r"><span class="jc-l">Official guidelines</span><div class="jc-t"><div class="jc-f" style="width:76.3%"></div></div><span class="jc-v">6.1</span></div><div class="jc-r"><span class="jc-l">Research paper</span><div class="jc-t"><div class="jc-f" style="width:68.8%"></div></div><span class="jc-v">5.5</span></div><div class="jc-r"><span class="jc-l">AI health assistant</span><div class="jc-t"><div class="jc-f" style="width:41.3%"></div></div><span class="jc-v">3.3</span></div><div class="jc-r"><span class="jc-l">Life coach</span><div class="jc-t"><div class="jc-f" style="width:41.3%"></div></div><span class="jc-v">3.3</span></div><div class="jc-r"><span class="jc-l">Forum member</span><div class="jc-t"><div class="jc-f" style="width:27.5%"></div></div><span class="jc-v">2.2</span></div><div class="jc-r"><span class="jc-l">Health influencer</span><div class="jc-t"><div class="jc-f" style="width:20%"></div></div><span class="jc-v">1.6</span></div></div><div><p class="jc-h">Claims attributed to each source (of 500)</p><div class="jc-r"><span class="jc-l">Forum member</span><div class="jc-t"><div class="jc-f dim" style="width:100%"></div></div><span class="jc-v">111</span></div><div class="jc-r"><span class="jc-l">Health influencer</span><div class="jc-t"><div class="jc-f dim" style="width:86.5%"></div></div><span class="jc-v">96</span></div><div class="jc-r"><span class="jc-l">Life coach</span><div class="jc-t"><div class="jc-f dim" style="width:62.2%"></div></div><span class="jc-v">69</span></div><div class="jc-r"><span class="jc-l">Registered dietitian</span><div class="jc-t"><div class="jc-f dim" style="width:45.9%"></div></div><span class="jc-v">51</span></div><div class="jc-r"><span class="jc-l">Medical specialist</span><div class="jc-t"><div class="jc-f dim" style="width:42.3%"></div></div><span class="jc-v">47</span></div><div class="jc-r"><span class="jc-l">Research paper</span><div class="jc-t"><div class="jc-f dim" style="width:40.5%"></div></div><span class="jc-v">45</span></div><div class="jc-r"><span class="jc-l">AI health assistant</span><div class="jc-t"><div class="jc-f dim" style="width:39.6%"></div></div><span class="jc-v">44</span></div><div class="jc-r"><span class="jc-l">Official guidelines</span><div class="jc-t"><div class="jc-f dim" style="width:33.3%"></div></div><span class="jc-v">37</span></div></div></div><p class="jc-insight"><b>The flip:</b>
955 the least-trusted sources are exactly the ones people most readily associate with everyday dietary claims — and AI sits in the ambiguous middle, neither rejected nor trusted like a clinician. K-means clustering of trust + attribution behaviour surfaced three attitude profiles — institution-focused, community-and-experience-driven, and mixed AI-open pragmatists — which, together with the interviews, shaped the three personas below.</p></div></div><div class="text-section afterone"><h1 class="client headersection">The three research personas</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">The interviews and the card-sorting clusters above condensed into three personas: the Adaptive Self-Manager, who dips in and out of JLAM as life changes; the Community-Oriented Support Seeker, who is there for people as much as for advice; and the Preventative Optimizer, who treats it as an information and decision-support tool. Each persona carries its own goals, frustrations and attitude towards AI, and every later design decision was checked against all three. Tap a card to view it full size.</p></div><div class="w-embed"><style>.jp-row{display:grid;grid-template-columns:1fr;gap:1.2em;margin:2em 0 3em}.jp-row a{display:block}.jp-row img{width:100%;border-radius:1.3em;display:block}@media(min-width:900px){.jp-row{grid-template-columns:1fr 1fr 1fr}}</style><div class="jp-row"><a href="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745824f160c39f3f37c4f7_jlam-persona-adaptive.jpg" target="_blank" rel="noopener"><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745824f160c39f3f37c4f7_jlam-persona-adaptive.jpg" alt="Persona: The Adaptive Self-Manager, 30-60+, uses JLAM intermittently depending on current needs" loading="lazy"></a><a href="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745824f160c39f3f37c544_jlam-persona-community.jpg" target="_blank" rel="noopener"><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745824f160c39f3f37c544_jlam-persona-community.jpg" alt="Persona: The Community-Oriented Support Seeker, 50-70+, uses JLAM actively seeking community and support" loading="lazy"></a><a href="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74582505d7481a0da313e2_jlam-persona-preventative.jpg" target="_blank" rel="noopener"><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74582505d7481a0da313e2_jlam-persona-preventative.jpg" alt="Persona: The Preventative Optimizer, 25-45, uses JLAM as an information and decision-support tool" loading="lazy"></a></div></div><img sizes="100px" loading="lazy" width="100" src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5.jpg" alt="The card-sorting template: eight sources from medical specialist to AI health assistant with the fifty dietary claim cards sorted beneath them" id="cardsort-template" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a74f6d34726d83e40dda6df_jlam-cardsort-template-v5.jpg 2400w" class="project-secondary-image"/><div class="text-section afterone"><h1 id="w-node-_12b8e759-38b5-7131-90ca-0471c18338e9-e0eb22fe" class="client headersection">Develop</h1><p id="w-node-_12b8e759-38b5-7131-90ca-0471c18338eb-e0eb22fe" data-w-id="12b8e759-38b5-7131-90ca-0471c18338eb" class="paragraph-5 headersection">Together with people with diabetes I ran a co-design workshop using value dams and flows; affinity diagramming showed what users actually want: data ownership, honest AI, organic connection instead of algorithmic matching, and tracking that doesn't feed obsession. From there the design went through three prototype versions.<span><br/><br/><strong>Version 1</strong> tested a chatbot-only MealCoach and deliberately light onboarding: to log or analyse a meal you talked to the AI, which made the feature feel opaque, and onboarding asked for personal data without explaining why.<br/><br/><strong>Version 2</strong> rebuilt MealCoach from a chatbot into a proper manual meal tool that AI enhances rather than replaces: barcode scanning, food-database search and custom entry next to the AI photo scan, with AI-vision limitations stated up front. JLAMs ZWEM metrics were reframed around data ownership - biometrics stored on the user's own device, never required to be shared, exportable and deletable on demand - and the UI was made more legible.<br/><br/><strong>Version 3</strong> added profile-visibility controls, identity verification, coach credential verification and a report-user affordance, and made AI disclosure continuous: assistants renamed to 'MealCoach AI' and 'ZWEMCoach AI', with an AI privacy screen right before first use - aligning with the transparency expectations of the EU AI Act.</span></p></div><img sizes="100px" loading="lazy" width="100" src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark.jpg" alt="Annotated MealCoach AI screens showing ethical design decisions: transparency labels, accuracy warnings, manual correction, and simplified nutrition data" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-2600.jpg 2600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark-p-3200.jpg 3200w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a744d29e91bbc7fa77e353a_jlam-findings-dark.jpg 3898w" class="project-secondary-image"/><div class="text-section afterone"><h1 class="client headersection">Humans, verified</h1>
955<p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">Round 3 also added human verification to the community: members prove they are a real person with a photo check, and coaches upload their qualifications before receiving a therapist label.</p></div><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2.jpg" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-2600.jpg 2600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2-p-3200.jpg 3200w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a745380c6abf33d9d1eeb7a_jlam-verification-dark-v2.jpg 4166w" alt="Round 3 profile and qualification verification flow, including a real photo check where members write JLAM on paper" class="project-secondary-image"/><div class="text-section afterone"><h1 id="w-node-_04c38176-60d6-e8e0-1505-da367b9e71da-e0eb22fe" class="client headersection">Test</h1><p id="w-node-_04c38176-60d6-e8e0-1505-da367b9e71dc-e0eb22fe" data-w-id="04c38176-60d6-e8e0-1505-da367b9e71dc" class="paragraph-5 headersection">Each prototype version got its own testing round.<span><br/><br/><strong>Round 1</strong> - an expert benchmark against JLAM's two existing apps - confirmed the hybrid's strengths in metric tracking and general UI, but exposed the chatbot-only MealCoach as under-explained: an AI-only meal flow triggered skepticism, pointing directly to the manual paths added in version 2.<br/><br/><strong>Round 2</strong> - six user sessions plus an eight-person survey poll - delivered the standout finding: people who walked through the flows rated MealCoach trust at 4.25/5, while people who only saw a walkthrough sat around the neutral midpoint at 3.48. The transparency cues that earn trust are invisible from the outside - they work when actually experienced.<br/><br/><strong>Round 3</strong> - a remote confirmation survey - landed feature success between 80% and 93%, with identity protection, onboarding, manual meal tracking and AI disclosure scoring highest. The two lowest items, both at 80%, were the historically trust-sensitive areas - coach vetting and AI-enhanced metric tracking - marking the remaining frontier rather than a failure. Overall app score: 4.40/5.</span></p></div><div class="text-section afterone"><h1 class="client headersection">Testing round 1 - the expert benchmark</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">V1 was tested in 11 one-on-one sessions with experts in diabetes, keto, diet and other chronic conditions. We walked through my app next to JLAM's old and new apps, part by part - registration, meal tracking, metric tracking, friend finding and general UI - and rated each part on Harris profiles from 1 to 5 for trust, ease, usefulness, clarity and aesthetics. The heatmap below shows where V1 stood: solid tracking and UI, but a chatbot-only MealCoach and an onboarding that people did not find useful.</p></div><div class="w-embed"><style>.jt-card{background:#181818;border:1px solid rgba(203,255,131,.25);border-radius:1.6em;padding:2.4em 2.6em;font-family:'Poppins',sans-serif;color:#fff;margin:1em 0 3em}.jt-tag{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#cbff83}.jt-card h4{font-size:19px;font-weight:600;color:#fff;margin:.45em 0 1.4em;line-height:1.3}.jt-hm{display:grid;grid-template-columns:minmax(8.5em,1.3fr) repeat(5,1fr);gap:6px;font-size:12.5px}.jt-hm .h{color:#9a9a9a;font-size:11.5px;display:flex;align-items:center;justify-content:center;padding:.4em 0}.jt-hm .r{color:#d9d9d9;display:flex;align-items:center;padding-right:.6em;line-height:1.25}.jt-hm .c{border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:600;padding:.85em 0}
955@media(max-width:900px){.jt-card{padding:1.8em 1.4em}.jt-hm{font-size:10px;grid-template-columns:minmax(5.5em,1.2fr) repeat(5,1fr)}.jt-hm .r{font-size:10px}}</style><div class="jt-card"><span class="jt-tag">Round 1 · V1 · 11 experts</span><h4>Average Harris ratings by exercise and criterion (0–5)</h4><div class="jt-hm"><span></span><span class="h">Trust</span><span class="h">Ease</span><span class="h">Usefulness</span><span class="h">Clarity</span><span class="h">Aesthetics</span><span class="r">Registration & onboarding</span><span class="c" style="background:rgba(203,255,131,.24);color:#fff">3.7</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="c" style="background:rgba(203,255,131,.1);color:#fff">3.3</span><span class="c" style="background:rgba(203,255,131,.28);color:#fff">3.8</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="r">MealCoach</span><span class="c" style="background:rgba(203,255,131,.1);color:#fff">3.4</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="c" style="background:rgba(203,255,131,.14);color:#fff">3.5</span><span class="c" style="background:rgba(203,255,131,.47);color:#0d0d0d">4.2</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="r">Metric tracking</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="c" style="background:rgba(203,255,131,.52);color:#0d0d0d">4.3</span><span class="c" style="background:rgba(203,255,131,.61);color:#0d0d0d">4.5</span><span class="r">Buddy feature</span><span class="c" style="background:rgba(203,255,131,.38);color:#fff">4.0</span><span class="c" style="background:rgba(203,255,131,.33);color:#fff">3.9</span><span class="c" style="background:rgba(203,255,131,.38);color:#fff">4.0</span><span class="c" style="background:rgba(203,255,131,.47);color:#0d0d0d">4.2</span><span class="c" style="background:rgba(203,255,131,.57);color:#0d0d0d">4.4</span><span class="r">General UI</span><span class="c" style="background:rgba(203,255,131,.42);color:#fff">4.1</span><span class="c" style="background:rgba(203,255,131,.47);color:#0d0d0d">4.2</span><span class="c" style="background:rgba(203,255,131,.42);color:#fff">4.1</span><span class="c" style="background:rgba(203,255,131,.47);color:#0d0d0d">4.2</span><span class="c" style="background:rgba(203,255,131,.61);color:#0d0d0d">4.5</span></div></div></div><div class="text-section afterone"><h1 class="client headersection">Testing round 2 - the general public</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">V2 was tested with 6 one-on-one sessions plus 8 survey poll responses from the general public, using the same Harris profiles. Every feature scored higher than in round 1 - the redesigned MealCoach made the biggest jump - and only the deliberately de-scoped buddy feature dipped slightly in the combined numbers.</p></div><div class="w-embed"><style>.ju-card{background:#181818;border:1px solid rgba(203,255,131,.25);border-radius:1.6em;padding:2.4em 2.6em;font-family:'Poppins',sans-serif;color:#fff;margin:1em 0 3em}.ju-tag{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#cbff83}.ju-card h4{font-size:19px;font-weight:600;color:#fff;margin:.45em 0 1.2em;line-height:1.3}.ju-legend{display:flex;gap:1.6em;flex-wrap:wrap;margin:0 0 1.6em;font-size:12px;color:#d9d9d9}.ju-legend i{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:.45em;vertical-align:-1px}.ju-groups{display:flex;justify-content:space-between;gap:1.2em;align-items:flex-end}.ju-g{flex:1;display:flex;flex-direction:column;align-items:center;gap:.7em;min-width:0}.ju-bars{display:flex;gap:6px;align-items:flex-end;height:190px}.ju-b{width:26px;border-radius:6px 6px 2px 2px;position:relative}.ju-b i{position:absolute;top:-1.55em;left:50%;transform:translateX(-50%);font-size:10.5px;font-style:normal;font-weight:600;color:#d9d9d9;white-space:nowrap}.ju-c1{background:#454545}.ju-c2{background:#cbff83}.ju-c3{background:rgba(203,255,131,.4)}.ju-gl{font-size:11.5px;color:#9a9a9a;text-align:center;line-height:1.3}
955@media(max-width:900px){.ju-card{padding:1.8em 1.4em}.ju-bars{height:150px}.ju-b{width:16px}.ju-b i{font-size:9px}.ju-groups{gap:.5em}}</style><div class="ju-card"><span class="ju-tag">Round 2 · V1 → V2 · 6 sessions + 8 surveys</span><h4>Average rating per feature, round 1 vs round 2</h4><div class="ju-legend"><span><i class="ju-c1"></i>Round 1</span><span><i class="ju-c2"></i>Round 2 · one-on-one</span><span><i class="ju-c3"></i>Round 2 · combined with survey ratings</span></div><div class="ju-groups"><div class="ju-g"><div class="ju-bars"><div class="ju-b ju-c1" style="height:76%"><i>3.80</i></div><div class="ju-b ju-c2" style="height:89.2%"><i>4.46</i></div><div class="ju-b ju-c3" style="height:87%"><i>4.35</i></div></div><span class="ju-gl">Onboarding</span></div><div class="ju-g"><div class="ju-bars"><div class="ju-b ju-c1" style="height:80.6%"><i>4.03</i></div><div class="ju-b ju-c2" style="height:85%"><i>4.25</i></div><div class="ju-b ju-c3" style="height:80%"><i>4.00</i></div></div><span class="ju-gl">Buddy feature</span></div><div class="ju-g"><div class="ju-bars"><div class="ju-b ju-c1" style="height:77.6%"><i>3.88</i></div><div class="ju-b ju-c2" style="height:93.8%"><i>4.69</i></div><div class="ju-b ju-c3" style="height:87.8%"><i>4.39</i></div></div><span class="ju-gl">MealCoach</span></div><div class="ju-g"><div class="ju-bars"><div class="ju-b ju-c1" style="height:87.6%"><i>4.38</i></div><div class="ju-b ju-c2" style="height:92.4%"><i>4.62</i></div><div class="ju-b ju-c3" style="height:88.2%"><i>4.41</i></div></div><span class="ju-gl">Metric tracking</span></div><div class="ju-g"><div class="ju-bars"><div class="ju-b ju-c1" style="height:83%"><i>4.15</i></div><div class="ju-b ju-c2" style="height:88.4%"><i>4.42</i></div><div class="ju-b ju-c3" style="height:87%"><i>4.35</i></div></div><span class="ju-gl">General UI</span></div></div></div></div><div class="text-section afterone"><h1 class="client headersection">Testing round 3 - the confirmation survey</h1><p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">Fifteen people from the general public completed task sets in the V3 prototype on their own, then answered a yes/no question per design objective and gave an overall 1-5 score. The binary questions kept people focused on whether each objective was achieved instead of tiny details: 80-93% answered yes per feature, and the app scored a mean of 4.40 out of 5.</p></div><div class="w-embed"><style>.jv-card{background:#181818;border:1px solid rgba(203,255,131,.25);border-radius:1.6em;padding:2.4em 2.6em;font-family:'Poppins',sans-serif;color:#fff;margin:1em 0 3em}.jv-tag{font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#cbff83}.jv-card h4{font-size:19px;font-weight:600;color:#fff;margin:.45em 0 1.2em;line-height:1.3}.jv-q{display:flex;flex-direction:column;gap:.85em}.jv-qr{display:grid;grid-template-columns:minmax(12em,17em) 1fr;gap:1.2em;align-items:center}.jv-ql{font-size:13.5px;color:#d9d9d9;line-height:1.3}.jv-qt{height:18px;border-radius:999px;background:rgba(255,255,255,.08)}.jv-qf{height:100%;border-radius:999px;background:#cbff83;display:flex;align-items:center;justify-content:flex-end}.jv-qf b{font-size:11px;color:#0d0d0d;font-weight:700;padding-right:.7em}.jv-statrow{display:flex;gap:2.5em;align-items:center;flex-wrap:wrap;margin-top:1.8em;border-top:1px solid rgba(255,255,255,.09);padding-top:1.6em}.jv-big{font-size:44px;font-weight:700;line-height:1;color:#fff}.jv-big span{font-size:18px;color:#9a9a9a;font-weight:500}.jv-note{font-size:12px;color:#9a9a9a;line-height:1.5;margin:.4em 0 0}.jv-chips{display:flex;gap:.7em;flex-wrap:wrap}.jv-chips span{font-size:12px;color:#d9d9d9;background:rgba(203,255,131,.12);border:1px solid rgba(203,255,131,.3);border-radius:999px;padding:.45em 1em}@media(max-width:900px){.jv-card{padding:1.8em 1.4em}.jv-qr{grid-template-columns:1fr;gap:.3em}}</style><div class="jv-card"><span class="jv-tag">Round 3 · V3 · n = 15</span><h4>“Yes” answers per design objective</h4><div class="jv-q"><div class="jv-qr"><span class="jv-ql">Forum protects identity</span><div class="jv-qt"><div class="jv-qf" style="width:87%"><b>87%</b></div></div></div><div class="jv-qr"><span class="jv-ql">Coaches are vetted</span><div class="jv-qt"><div class="jv-qf" style="width:80%"><b>80%</b></div></div></div><div class="jv-qr"><span class="jv-ql">Onboarding is useful</span><div class="jv-qt"><div class="jv-qf" style="width:93%"><b>93%</b></div></div></div><div class="jv-qr"><span class="jv-ql">Manual meal tracking is useful</span><div class="jv-qt"><div class="jv-qf" style="width:93%"><b>93%</b></div></div></div><div class="jv-qr"><span class="jv-ql">AI enhances meal tracking</span><div class="jv-qt"><div class="jv-qf" style="width:87%"><b>87%</b></div></div></div><div class="jv-qr"><span class="jv-ql">Weekly metric tracking is useful</span><div class="jv-qt"><div class="jv-qf" style="width:87%"><b>87%</b></div></div></div><div class="jv-qr"><span class="jv-ql">AI enhances metric tracking</span><div class="jv-qt"><div class="jv-qf" style="width:80%"><b>80%</b></div></div></div><div class="jv-qr"><span class="jv-ql">AI disclosure always clear</span><div class="jv-qt"><div class="jv-qf" style="width:93%"><b>93%</b></div></div></div></div><div class="jv-statrow"><div><div class="jv-big">4.40<span> / 5</span></div><p class="jv-note">Mean overall score</p></div><div class="jv-chips"><span>1× scored 3</span><span>7× scored 4</span><span>7× scored 5</span></div></div></div></div><div class="text-section afterone"><h1 class="client headersection">The poster presentation</h1>
955<p data-w-id="ca10b1da-a58f-936f-cc47-b0a0db0001f7" class="paragraph-5 headersection">AI as a Tool, Not a Feature was my master's graduation client project - a full year of research, design and testing with JLAM that was graded an 8. I still volunteer as a UX consultant for the organisation two to three times a month. And if you would like to see this case study in a more visual format, the full poster is on Figma:</p></div><a id="poster-btn" href="https://www.figma.com/design/wtbnU92E87WyjLWYkNMz4l/Graduation-poster?node-id=0-1&t=OvqTKW2Zl0N4oUsh-1" class="outline-button item">Check out poster presentation</a><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced.jpg" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced-p-500.jpg 500w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced-p-800.jpg 800w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced-p-1080.jpg 1080w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced-p-1600.jpg 1600w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced-p-2000.jpg 2000w, https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a7485dde1686a60040c6972_jlam-grad-poster-enhanced.jpg 2560w" alt="Armandas holding the printed JLAM graduation poster with the Discover, Define, Develop and Test story" class="project-secondary-image"/></div><div class="w-embed"><div class='cs-next'> 956<h2>More projects</h2> 957<div class='cs-next-grid'> 958<a class='cs-pc' style='--c:#9f80ff' href='/kamernet-internship'><span class='bgp' style="background-image:url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a735a8e79275115f4c8bb62_hw-kamernet-internship.jpg')"></span><span class='tg'>UX Research · Web Design</span><h3>Kamernet Internship</h3><p>Research with 51 internationals; user-scored design rounds that beat the live site every time; a shipped How-It-Works redesign.</p><span class='go'>View case study →</span></a> 959<a class='cs-pc' style='--c:#9f80ff' href='/kamernet-redesign'><span class='bgp' style="background-image:url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a735a8e68b6f6d602ab4f51_hw-kamernet-redesign.jpg')"></span><span class='tg'>Web Design · UX Research</span><h3>Kamernet Redesign</h3><p>End-to-end account page redesign of the rental platform — flows, UI and a systemised component set.</p><span class='go'>View case study →</span></a> 960</div> 961</div></div></section><div class="contact-page-wrapper"><section id="Contact" class="section contact-section"><div class="main-container contact-wrapper w-container"><div class="contact-info"><h5 id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f6515c-e0f65159" class="contact-heading">Contacts</h5><div data-w-id="b59307a5-2e9a-d83f-8b77-7072e0f6515e" class="mail-to-wrapper mail-to-wrap"><a data-w-id="b59307a5-2e9a-d83f-8b77-7072e0f6515f" href="mailto:[email protected]?subject=Work%20Opportunities" class="copy-btn w-inline-block"><img src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/679d63c04202ad9bf67ca2d2_Arrow-dark.svg" loading="lazy" alt="Mail Me" class="medium-arrow"/></a><a id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f65161-e0f65159" href="mailto:mailto:[email protected]?subject=Work%20Opportunities" class="link-2">[email protected]</a></div><div class="contact-address-tell"><div id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f65164-e0f65159" class="address">Den Haag, Netherlands</div><a id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f65166-e0f65159" href="tel:+31641580477" class="phone">+31 641 580 477</a></div></div><div class="contact-socials"><a data-w-id="b59307a5-2e9a-d83f-8b77-7072e0f65169" href="https://www.linkedin.com/in/armandas-aksomaitis-474a80150/" target="_blank" class="social-footer-item w-inline-block"><div class="footer-link-slider"><div id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f6516b-e0f65159" class="footer-link-text">LinkedIn</div></div></a>
961<div class="contact-address-tell sm-contact-address-tell"><div id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f6519a-e0f65159" class="address link-4">Based in NL</div><a id="w-node-b59307a5-2e9a-d83f-8b77-7072e0f6519c-e0f65159" href="tel:+1000000000" class="link-4 tell-link">+31 641 580 477</a></div></div></div></section></div><div class="w-embed"><style> 962section.contact-section{display:none!important} 963:where(#ct-root,#ct-root *){box-sizing:border-box;margin:0;padding:0} 964#ct-root{font-family:'Poppins',sans-serif;font-size:16px;color:#fff;line-height:1.5} 965#ct-root .ct-sec{position:relative;padding:110px 0;overflow:hidden;background:#000;text-align:center} 966#ct-root .ct-sec::before{content:'';position:absolute;inset:0;background:url('https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a72899617577ce78b2850fc_divider-team-violet.jpg') center/cover no-repeat} 967#ct-root .ct-sec::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,#000 0%,rgba(0,0,0,.55) 25%,rgba(0,0,0,.6) 75%,#000 100%)} 968#ct-root .ct-wrap{position:relative;z-index:1;width:83.33%;margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:26px} 969#ct-root h2{font-size:3.45em;line-height:1;font-weight:700;max-width:14em} 970#ct-root .ct-row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center} 971#ct-root .ct-btn{display:inline-flex;align-items:center;justify-content:center;border-radius:2em;font-weight:600;font-size:16px;padding:16px 38px;text-decoration:none;transition:transform .2s} 972#ct-root .ct-w{background:#fff;color:#111;border:3px solid #fff} 973#ct-root .ct-o{border:2.5px solid #fff;color:#fff;background:transparent} 974#ct-root .ct-btn:hover{transform:translateY(-2px)} 975#ct-root .ct-o:hover{background:#fff;color:#111} 976#ct-root .ct-meta{color:#9a9a9a;font-size:13px} 977#ct-root .ct-meta a{color:#d9d9d9;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.2);transition:color .2s,border-color .2s} 978#ct-root .ct-meta a:hover{color:#fff;border-color:#99dcff} 979@media(max-width:900px){#ct-root h2{font-size:clamp(30px,8.5vw,3.45em)}#ct-root .ct-sec{padding:80px 0}} 980</style> 981<div id='ct-root'><section class='ct-sec' id='Contact'><div class='ct-wrap'> 982<h2>Have a role in mind? Let’s talk.</h2> 983<div class='ct-row'> 984<a class='ct-btn ct-w' href='mailto:[email protected]'>[email protected]</a> 985<a class='ct-btn ct-o' href='tel:+31641580477'>+31 641 580 477</a> 986</div> 987<div class='ct-meta'>Or connect on <a href='https://www.linkedin.com/in/armandas-aksomaitis-474a80150/' target='_blank' rel='noopener'>LinkedIn</a> · The Hague, NL</div> 988</div></section></div></div><div class="w-embed"><style> 989:where(#ft-root,#ft-root *){box-sizing:border-box;margin:0;padding:0} 990#ft-root{font-family:'Poppins',sans-serif;background:#000;color:#9a9a9a;font-size:13px;padding:30px 0 46px} 991#ft-root .ft-wrap{width:83.33%;margin:0 auto;display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap} 992#ft-root a{color:#d9d9d9;text-decoration:none;border-bottom:1px solid transparent;padding-bottom:2px;transition:color .2s,border-color .2s} 993#ft-root a:hover{color:#fff;border-color:var(--lc)} 994</style> 995<div id='ft-root'><div class='ft-wrap'> 996<span>© 2026 Armandas Aksomaitis</span> 997<span><a href='https://www.linkedin.com/in/armandas-aksomaitis-474a80150/' target='_blank' rel='noopener' style='--lc:#99dcff'>LinkedIn</a> · <a href='mailto:[email protected]' style='--lc:#9f80ff'>Email</a> · <a href='https://www.youtube.com/watch?v=TR7AWysTBzI' target='_blank' rel='noopener' style='--lc:#db2ffb'>Video CV</a> · <a href='/about' style='--lc:#b6f24a'>My story</a></span> 998</div></div></div><div class="w-embed w-script"><style> 999:where(#axf-root,#axf-root *){box-sizing:border-box;margin:0;padding:0} 1000#axf-root{font-family:'Poppins',sans-serif} 1001#axf-root .axf-fab{position:fixed;right:26px;bottom:84px;z-index:9990;width:62px;height:62px;border-radius:50%;border:0;cursor:pointer;background:#b6f24a;box-shadow:0 10px 26px rgba(0,0,0,.5);display:grid;place-items:center} 1002#axf-root .axf-fab svg{width:26px;height:26px;fill:#111} 1003#axf-root .axf-fab::after{content:'';position:absolute;inset:-7px;border-radius:50%;border:1.5px solid rgba(182,242,74,.4);animation:axfp 2.6s infinite} 1004@keyframes axfp{0%{transform:scale(.86);opacity:.9}70%{transform:scale(1.14);opacity:0}
1004100%{opacity:0}} 1005#axf-root .axf-panel{position:fixed;right:26px;bottom:160px;z-index:9989;width:min(354px,calc(100vw - 40px));background:#181818;border:.12em solid #9f80ff;border-radius:1.6em;overflow:hidden;box-shadow:0 30px 70px rgba(0,0,0,.7);transform:translateY(14px);opacity:0;pointer-events:none;transition:transform .25s,opacity .25s;font-size:16px;color:#fff;line-height:1.5} 1006#axf-root .axf-panel.on{transform:none;opacity:1;pointer-events:auto} 1007#axf-root .axf-head{display:flex;align-items:center;gap:11px;padding:16px 18px;border-bottom:1px solid rgba(255,255,255,.08)} 1008#axf-root .axf-ava{flex:none;width:36px;height:31px;object-fit:contain} 1009#axf-root .axf-head b{font-size:14.5px;font-weight:600;display:block} 1010#axf-root .axf-head span{font-size:11.5px;color:#9a9a9a} 1011#axf-root .axf-x{margin-left:auto;background:none;border:0;color:#9a9a9a;font-size:22px;line-height:1;cursor:pointer;font-family:inherit} 1012#axf-root .axf-x:hover{color:#fff} 1013#axf-root .axf-msgs{height:250px;overflow-y:auto;padding:16px 18px;display:flex;flex-direction:column;gap:10px;scrollbar-width:thin;scrollbar-color:rgba(159,128,255,.45) transparent} 1014#axf-root .axf-msgs::-webkit-scrollbar{width:6px} 1015#axf-root .axf-msgs::-webkit-scrollbar-track{background:transparent} 1016#axf-root .axf-msgs::-webkit-scrollbar-thumb{background:rgba(159,128,255,.45);border-radius:3px} 1017.axf-m{max-width:84%;padding:11px 15px;border-radius:1.3em;font-size:13.5px;line-height:1.5} 1018.axf-ai{align-self:flex-start;background:#0d0d0d;border:1px solid rgba(159,128,255,.3);color:#d9d9d9;border-bottom-left-radius:.45em} 1019.axf-ai b{color:#fff;font-weight:600} 1020.axf-ai ul{margin:6px 0;padding-left:16px} 1021.axf-ai li{margin:3px 0} 1022.axf-ai hr{border:0;border-top:1px solid rgba(255,255,255,.14);margin:9px 0} 1023.axf-ai a{color:#b6f24a;text-decoration:underline} 1024.axf-me{align-self:flex-end;background:#fff;color:#111;font-weight:500;border-bottom-right-radius:.45em} 1025.axf-links{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px} 1026a.axf-pl{display:inline-flex;padding:6px 13px;border:1.5px solid #9f80ff;border-radius:2em;color:#fff;font-size:12px;font-weight:600;text-decoration:none} 1027a.axf-pl:hover{background:#9f80ff;color:#0d0d0d} 1028a.axf-pl.lm{border-color:#b6f24a} 1029a.axf-pl.lm:hover{background:#b6f24a;color:#0d0d0d} 1030.axf-tp{align-self:flex-start;display:inline-flex;gap:5px;padding:12px 16px;background:#0d0d0d;border:1px solid rgba(159,128,255,.3);border-radius:1.3em;border-bottom-left-radius:.45em} 1031.axf-tp i{width:6px;height:6px;border-radius:50%;background:#9f80ff;animation:axtp2 1s infinite} 1032.axf-tp i:nth-child(2){animation-delay:.15s} 1033.axf-tp i:nth-child(3){animation-delay:.3s} 1034@keyframes axtp2{0%,60%,100%{opacity:.25}30%{opacity:1}} 1035#axf-root .axf-chips{display:flex;gap:7px;flex-wrap:wrap;padding:10px 18px 12px;border-top:1px solid rgba(255,255,255,.06)} 1036#axf-root .axf-c{flex:none;border:1.5px solid rgba(255,255,255,.28);background:transparent;color:#d9d9d9;font-family:inherit;font-size:12px;font-weight:500;padding:7px 14px;border-radius:2em;cursor:pointer} 1037#axf-root .axf-c:hover{border-color:#9f80ff;color:#fff} 1038#axf-root .axf-in{display:flex;gap:9px;padding:0 18px 18px} 1039#axf-root .axf-in input{flex:1;min-width:0;background:#0d0d0d;border:1.5px solid rgba(255,255,255,.16);border-radius:2em;padding:12px 17px;color:#fff;font-family:inherit;font-size:13px} 1040#axf-root .axf-in input:focus{outline:none;border-color:#9f80ff} 1041#axf-root .axf-send{flex:none;width:42px;height:42px;border-radius:50%;border:0;background:#9f80ff;cursor:pointer;display:grid;place-items:center} 1042#axf-root .axf-send svg{width:17px;height:17px;fill:#0d0d0d} 1043@media(prefers-reduced-motion:reduce){#axf-root *,.axf-tp i{animation:none!important;transition:none!important}} 1044</style> 1045<div id='axf-root'> 1046<div class='axf-panel' id='axf-panel' role='dialog' aria-label='Ask about Armandas'> 1047<div class='axf-head'><img class='axf-ava' src='https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/6a732513f19eabc129e113ce_ai-assistant-avatar.png' alt=''><div><b>Ask about Armandas</b><span>Powered by Claude</span></div><button class='axf-x' id='axf-x' aria-label='Close'>×</button></div> 1048<div class='axf-msgs' id='axf-msgs'><div class='axf-m axf-ai'>Hi! Ask me anything about Armandas or his work.</div></div> 1049<div class='axf-chips'> 1050<button class='axf-c' data-k='hire'>Is he available?</button> 1051<button class='axf-c' data-k='ai'>His AI work?</button> 1052</div> 1053<div class='axf-in'><input id='axf-inp' type='text' placeholder='Ask a question…' aria-label='Ask a question'><button class='axf-send' id='axf-snd' aria-label='Send'><svg viewBox='0 0 24 24'><path d='M2 21l21-9L2 3v7l15 2-15 2z'/></svg></button></div> 1054</div> 1055<button class='axf-fab' id='axf-fab' aria-label='Open the AI assistant'><svg viewBox='0 0 24 24'><path d='M12 2a9 9 0 0 0-9 9c0 1.8.53 3.47 1.44 4.88L3 21l5.3-1.36A8.96 8.96 0 0 0 12 20a9 9 0 0 0 0-18zm-4 8h8v2H8v-2zm0-3h8v2H8V7zm0 6h5v2H8v-2z'/></svg></button> 1056</div>
1057<script> 1058(function(){var f=document.getElementById('axf-fab'),p=document.getElementById('axf-panel'); 1059f.addEventListener('click',function(){p.classList.toggle('on')}); 1060document.getElementById('axf-x').addEventListener('click',function(){p.classList.remove('on')}); 1061if(!window.axWire){ 1062var CAN={ 1063ai:{t:'He designs and ships working AI agents â like the meal-analysis agent inside the JLAM lifestyle-medicine platform, and this assistant youâre talking to right now.',l:[['JLAM AI case study â','/jlam']]}, 1064hire:{t:'Yes â heâs based in The Hague and open to UX / AI design roles. Fastest way to reach him:',l:[['[email protected]','mailto:[email protected]',1],['+31 641 580 477','tel:+31641580477',1]]}, 1065err:{t:'I couldnât reach the server just now â you can email Armandas directly.',l:[['[email protected]','mailto:[email protected]',1]]}}; 1066function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML} 1067function md(s){ 1068s=esc(s); 1069s=s.replace(/\*\*([^*]+)\*\*/g,'<b>$1</b>'); 1070s=s.replace(/(^|[\s(])\*([^*\n]+)\*/g,'$1<i>$2</i>'); 1071s=s.replace(/([\w.+-]+@[\w-]+\.[a-z]{2,})/gi,'<a href="mailto:$1">$1</a>'); 1072var L=s.split('\n'),o='',ul=false,i,ln; 1073for(i=0;i<L.length;i++){ln=L[i]; 1074if(/^\s*- /.test(ln)){if(!ul){o+='<ul>';ul=true}o+='<li>'+ln.replace(/^\s*- /,'')+'</li>'} 1075else{if(ul){o+='</ul>';ul=false} 1076if(/^\s*-{3,}\s*$/.test(ln)){o+='<hr>'} 1077else if(ln.replace(/\s/g,'')===''){if(o&&o.slice(-4)!=='<br>'&&o.slice(-5)!=='</ul>'&&o.slice(-4)!=='<hr>'){o+='<br>'}} 1078else{o+=ln+'<br>'}}} 1079if(ul){o+='</ul>'} 1080return o.replace(/(<br>)+$/,'')} 1081window.axWire=function(msgsEl,inputEl,sendEl,chipEls,pfx){ 1082var hist=[]; 1083function scr(){msgsEl.scrollTop=msgsEl.scrollHeight} 1084function put(cls,html,txt){var d=document.createElement('div');d.className=pfx+'-m '+cls;if(html!=null){d.innerHTML=html}else{d.textContent=txt}msgsEl.appendChild(d);scr();return d} 1085function ans(a){var h=esc(a.t);if(a.l){h+='<span class="'+pfx+'-links">';for(var i=0;i<a.l.length;i++){var x=a.l[i];h+='<a class="'+pfx+'-pl'+(x[2]?' lm':'')+'" href="'+x[1]+'">'+esc(x[0])+'</a>'}h+='</span>'}put(pfx+'-ai',h)} 1086function typing(){var d=document.createElement('div');d.className=pfx+'-tp';d.innerHTML='<i></i><i></i><i></i>';msgsEl.appendChild(d);scr();return d} 1087function chip(k,q){put(pfx+'-me',null,q);var t=typing();setTimeout(function(){t.remove();ans(CAN[k])},800)} 1088function free(q){put(pfx+'-me',null,q);hist.push({role:'user',content:q});var t=typing(); 1089fetch('https://claude.armandasa.workers.dev',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode:'recruiter',messages:hist})}) 1090.then(function(r){return r.json()}) 1091.then(function(d){var x='';if(d){if(d.content&&d.content[0]&&d.content[0].text){x=d.content[0].text}else if(typeof d.text==='string'){x=d.text}else if(typeof d.reply==='string'){x=d.reply}else if(typeof d.message==='string'){x=d.message}} 1092t.remove();if(x){hist.push({role:'assistant',content:x});put(pfx+'-ai',md(x))}else{ans(CAN.err)}}) 1093.catch(function(){t.remove();ans(CAN.err)})} 1094for(var i=0;i<chipEls.length;i++){(function(c){c.addEventListener('click',function(){chip(c.getAttribute('data-k'),c.textContent)})})(chipEls[i])} 1095function sub(){var v=inputEl.value.trim();if(!v)return;inputEl.value='';free(v)} 1096sendEl.addEventListener('click',sub); 1097inputEl.addEventListener('keydown',function(e){if(e.key==='Enter')sub()}); 1098}; 1099} 1100window.axWire(document.getElementById('axf-msgs'),document.getElementById('axf-inp'),document.getElementById('axf-snd'),Array.prototype.slice.call(document.querySelectorAll('#axf-root .axf-c')),'axf'); 1101})(); 1102</script>
1102</div></div>
1102<script src="https://d3e54v103j8qbb.cloudfront.net/js/jquery-3.5.1.min.dc5e7f18c8.js?site=679d63c04202ad9bf67ca282" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
1102<script src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/js/webflow.schunk.1a807f015b216e46.js" type="text/javascript" integrity="sha384-oY4R8SfnVBTWnix6ULiWbZs/5tyRyxEiWcG+WrT3unnu5pmfsIYJEraZcyFVlvND" crossorigin="anonymous"></script>
1102<script src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/js/webflow.schunk.a00a8b420c52b68b.js" type="text/javascript" integrity="sha384-qNDIfddkcA6nxzhr5sHcZYAbws39D4Rji8wZijzTCdASax5iHJi74TMMcWixxzt2" crossorigin="anonymous"></script>
1102<script src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282/js/webflow.17bef9ad.27d1a2b19c068a6d.js" type="text/javascript" integrity="sha384-yqdhHi3D4bQb6kxHNTXlhK90gJG4kzju7YPdwfPOaKaq/7+1MT5SwYzZCMzt5okn" crossorigin="anonymous"></script>
1102<style> 1103/* Lime "thinking" state shared by the homepage AI section and the sticky AI panel. 1104 Driven by each widget's own typing indicator, so it covers the send button, 1105 Enter, the preset chips and error paths identically. */ 1106#ax-root .ax-chat,#axf-root .axf-panel{transition:border-color .75s ease,box-shadow .75s ease} 1107#ax-root .ax-chat::before{transition:opacity .75s ease} 1108#ax-root .ax-send,#axf-root #axf-snd{transition:background-color .6s ease} 1109#ax-root .ax-ai,#ax-root .ax-in input, 1110#axf-root .axf-ai,#axf-root #axf-inp{transition:border-color .75s ease} 1111/* the corner glow is the section widget only â the sticky panel has no gradient */ 1112#ax-root .axm-glow{position:absolute;inset:0;pointer-events:none;opacity:0; 1113 border-radius:inherit;background:linear-gradient(45deg,rgba(0,0,0,0) 52%,#b6f24a); 1114 transition:opacity .75s ease} 1115 1116#ax-root .ax-chat.axm-think,#ax-root .ax-chat.axm-done, 1117#axf-root .axf-panel.axm-think,#axf-root .axf-panel.axm-done{border-color:#b6f24a; 1118 transition:border-color .22s ease-out,box-shadow .22s ease-out} 1119#ax-root .ax-chat.axm-think::before,#ax-root .ax-chat.axm-done::before{opacity:0; 1120 transition:opacity .22s ease-out} 1121#ax-root .axm-think .axm-glow,#ax-root .axm-done .axm-glow{opacity:.9; 1122 transition:opacity .22s ease-out} 1123#ax-root .axm-think .ax-send,#ax-root .axm-done .ax-send, 1124#axf-root .axm-think #axf-snd,#axf-root .axm-done #axf-snd{background:#b6f24a; 1125 transition:background-color .22s ease-out} 1126#ax-root .axm-think .ax-ai,#ax-root .axm-done .ax-ai, 1127#axf-root .axm-think .axf-ai,#axf-root .axm-done .axf-ai{border-color:rgba(182,242,74,.5); 1128 transition:border-color .22s ease-out} 1129#ax-root .axm-think .ax-tp,#axf-root .axm-think .axf-tp{border-color:rgba(182,242,74,.5)} 1130#ax-root .axm-think .ax-tp i,#axf-root .axm-think .axf-tp i{background:#b6f24a} 1131#ax-root .axm-think .ax-in input:focus,#ax-root .axm-done .ax-in input:focus, 1132#axf-root .axm-think #axf-inp:focus,#axf-root .axm-done #axf-inp:focus{border-color:#b6f24a} 1133#ax-root .ax-chat.axm-think{box-shadow:0 0 0 3px rgba(182,242,74,.22); 1134 animation:axmB 1.9s ease-in-out .3s infinite} 1135#ax-root .ax-chat.axm-done{animation:axmP 1.15s ease-out} 1136/* the sticky panel keeps its drop shadow underneath the lime ring */ 1137#axf-root .axf-panel.axm-think{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 3px rgba(182,242,74,.22); 1138 animation:axmBF 1.9s ease-in-out .3s infinite} 1139#axf-root .axf-panel.axm-done{animation:axmPF 1.15s ease-out} 1140@keyframes axmB{0%,100%{box-shadow:0 0 0 3px rgba(182,242,74,.22)} 114150%{box-shadow:0 0 0 7px rgba(182,242,74,.09)}} 1142@keyframes axmP{0%{box-shadow:0 0 0 3px rgba(182,242,74,.22)} 114320%{box-shadow:0 0 0 10px rgba(182,242,74,.32)} 114445%{box-shadow:0 0 0 3px rgba(182,242,74,.14)} 114568%{box-shadow:0 0 0 9px rgba(182,242,74,.26)} 1146100%{box-shadow:0 0 0 3px rgba(182,242,74,0)}} 1147@keyframes axmBF{0%,100%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 3px rgba(182,242,74,.22)} 114850%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 7px rgba(182,242,74,.09)}} 1149@keyframes axmPF{0%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 3px rgba(182,242,74,.22)} 115020%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 10px rgba(182,242,74,.32)} 115145%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 3px rgba(182,242,74,.14)} 115268%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 9px rgba(182,242,74,.26)} 1153100%{box-shadow:0 30px 70px rgba(0,0,0,.7),0 0 0 3px rgba(182,242,74,0)}} 1154</style>
1155<script> 1156(function(){ 1157function wire(msgsId,panelSel,tp,glow){ 1158var msgs=document.getElementById(msgsId),panel=document.querySelector(panelSel); 1159if(!msgs||!panel||panel.__axm)return; 1160panel.__axm=1; 1161if(getComputedStyle(panel).position==='static')panel.style.position='relative'; 1162if(glow&&!panel.querySelector('.axm-glow')){ 1163var g=document.createElement('div');g.className='axm-glow'; 1164panel.insertBefore(g,panel.firstChild)} 1165var on=false,dto=0; 1166function up(){ 1167var busy=!!msgs.querySelector('.'+tp); 1168if(busy===on)return; 1169on=busy; 1170if(busy){clearTimeout(dto);panel.classList.remove('axm-done');panel.classList.add('axm-think'); 1171dispatchEvent(new CustomEvent('axm:think'))} 1172else{panel.classList.remove('axm-think');panel.classList.add('axm-done'); 1173clearTimeout(dto);dto=setTimeout(function(){panel.classList.remove('axm-done')},1150); 1174dispatchEvent(new CustomEvent('axm:done'))}} 1175new MutationObserver(up).observe(msgs,{childList:true,subtree:true}); 1176up()} 1177function boot(){ 1178wire('ax-msgs','#ax-root .ax-chat','ax-tp',1); 1179wire('axf-msgs','#axf-panel','axf-tp',0)} 1180if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot);else boot(); 1181/* the fab panel may be built lazily on some pages */ 1182setTimeout(boot,1200);setTimeout(boot,4000); 1183})(); 1184</script>
vendor: 1 bytes, line 1184
1184
1185<script> 1186(function(){ 1187/* Chat-header avatar. Swaps the flat PNG for the same face the big mascot wears, 1188 so it can look at the cursor and react. This lives in the site footer rather 1189 than in the AI Launcher component, because that component is a single embed 1190 with ~4k spare and the working chat sits inside it - nothing here touches it. 1191 Purple stays purple; lime is only ever a panel state colour. */ 1192var FINE=matchMedia('(pointer:fine)').matches, 1193RED=matchMedia('(prefers-reduced-motion:reduce)').matches; 1194function N(v){return Math.round(v*10)/10} 1195/* the mascot's own two-cubic shape: [halfWidth, baselineY, bulgeUp, bulgeDown]. 1196 A negative down-bulge bends the open eye into a closed crescent, so faces 1197 morph as geometry instead of cross-fading two stacked shapes. */ 1198function sh(cx,w,y,u,d){var x=N(cx-w),z=N(cx+w),t=N(y-u*1.3333),b=N(y+d*1.3333);y=N(y); 1199return'M'+x+' '+y+'C'+x+' '+t+' '+z+' '+t+' '+z+' '+y+'C'+z+' '+b+' '+x+' '+b+' '+x+' '+y+'Z'} 1200function eyeD(cx,a){return sh(cx,25-4*a,134+9*a,25-6.5*a,25-34.5*a)} 1201function mouthD(o){return sh(138,31+3*o,168-6*o,0,29+12*o)} 1202var SVG='<svg viewBox="0 0 274 240" style="width:100%;height:100%;display:block;overflow:visible">'+ 1203'<g fill="#B173FE">'+ 1204'<g><path d="M96 12 79 33 89 33 84 47 101 26 91 26Z"/><rect x="87" y="40" width="5" height="20" rx="2.5"/><circle cx="90" cy="60" r="9"/></g>'+ 1205'<g><path d="M195 8 178 29 188 29 183 43 200 22 190 22Z"/><rect x="185" y="40" width="5" height="20" rx="2.5"/><circle cx="188" cy="60" r="9"/></g>'+ 1206'<rect x="7" y="117" width="31" height="70" rx="11"/><rect x="238" y="117" width="31" height="70" rx="11"/>'+ 1207'<rect x="45" y="67" width="186" height="168" rx="45"/></g>'+ 1208'<path class="ya1" fill="#9CDCFE"/><path class="ya2" fill="#9CDCFE"/>'+ 1209'<g class="yp1"><circle cx="92" cy="134" r="12" fill="#141414"/>'+ 1210'<circle cx="87" cy="128" r="4.6" fill="#fff"/><circle cx="97" cy="139" r="2.3" fill="#fff"/></g>'+ 1211'<g class="yp2"><circle cx="183" cy="134" r="12" fill="#141414"/>'+ 1212'<circle cx="178" cy="128" r="4.6" fill="#fff"/><circle cx="188" cy="139" r="2.3" fill="#fff"/></g>'+ 1213'<path class="ymo" fill="#9CDCFE"/></svg>'; 1214/* pupil travel maxes at ~6 of the eye's 25 radius, so it never needs clipping */ 1215 1216function upgrade(img){ 1217if(!img||img.__axa)return;img.__axa=1; 1218var b=img.getBoundingClientRect(),w=b.width||img.width||36,h=b.height||img.height||31; 1219var box=document.createElement('span'); 1220box.className='axm-ava'; 1221box.style.cssText='display:inline-block;line-height:0;vertical-align:middle;width:'+w+'px;height:'+h+'px'; 1222box.innerHTML=SVG; 1223img.parentNode.insertBefore(box,img); 1224img.style.display='none'; 1225var svg=box.firstChild, 1226E={a1:svg.querySelector('.ya1'),a2:svg.querySelector('.ya2'), 1227p1:svg.querySelector('.yp1'),p2:svg.querySelector('.yp2'),mo:svg.querySelector('.ymo')}; 1228var a=0,o=0,ta=0,to=0,bk=0,bt=2+Math.random()*4,hold=0, 1229mx=innerWidth/2,my=0,raf=0,lt=0,live=false; 1230 1231function paint(){ 1232var by=bk>0?1-Math.sin((1-bk/.16)*3.1416)*.86:1; 1233E.a1.setAttribute('d',eyeD(92,a));E.a2.setAttribute('d',eyeD(183,a)); 1234E.mo.setAttribute('d',mouthD(o)); 1235var r=svg.getBoundingClientRect(),dx=0,dy=0;
1236if(r.width&&FINE){var gx=mx-(r.left+r.width/2),gy=my-(r.top+r.height/2), 1237d=Math.sqrt(gx*gx+gy*gy)||1; 1238dx=gx/d*Math.min(7,d*.09);dy=gy/d*Math.min(6,d*.08)} 1239var ps=N(Math.max(0,1-a*1.4)),sy=N(ps*by),t='translate('+N(dx)+','+N(dy)+')'; 1240E.p1.setAttribute('transform',t+'translate(92,134)scale('+ps+','+sy+')translate(-92,-134)'); 1241E.p2.setAttribute('transform',t+'translate(183,134)scale('+ps+','+sy+')translate(-183,-134)'); 1242E.a1.setAttribute('transform','translate(92,134)scale(1,'+N(by)+')translate(-92,-134)'); 1243E.a2.setAttribute('transform','translate(183,134)scale(1,'+N(by)+')translate(-183,-134)')} 1244 1245function frame(now){ 1246if(!live){raf=0;return} 1247raf=requestAnimationFrame(frame); 1248var dt=lt?Math.min((now-lt)/1000,.05):.016;lt=now; 1249if(hold>0){hold-=dt;if(hold<=0){ta=0;to=0}} 1250var k=Math.min(1,dt*12);a+=(ta-a)*k;o+=(to-o)*k; 1251bt-=dt;if(bt<=0){if(ta<.5&&a<.3)bk=.16;bt=3+Math.random()*5} 1252if(bk>0)bk-=dt; 1253paint()} 1254function run(){if(!live||raf)return;lt=0;raf=requestAnimationFrame(frame)} 1255 1256/* only animate while the header is actually on screen - the fab panel is closed 1257 most of the time and this script runs on every page of the site */ 1258if('IntersectionObserver'in window) 1259new IntersectionObserver(function(en){live=en[0].isIntersecting&&!RED;if(live)run()}).observe(box); 1260else{live=!RED;run()} 1261 1262if(FINE)addEventListener('mousemove',function(e){mx=e.clientX;my=e.clientY},{passive:true}); 1263addEventListener('axm:think',function(){ta=0;to=1;hold=0;run()}); 1264addEventListener('axm:done',function(){ta=1;to=0;hold=1.7;run()}); 1265paint()} 1266 1267function scan(){ 1268document.querySelectorAll('img.axf-ava,#ax-root img.ax-ava').forEach(upgrade)} 1269if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',scan);else scan(); 1270/* the fab panel is built lazily on some pages, same as the lime wiring above */ 1271setTimeout(scan,1200);setTimeout(scan,4000); 1272})(); 1273</script>
vendor: 1 bytes, line 1273
1273
1274<script> 1275(function(){ 1276/* Mobile: the nav is a fixed pill, so page content slides underneath it and the 1277 clearance below it is whatever each page's own first section sets - and those 1278 live inside per-page embeds, so there is no selector to target. Measure it 1279 instead, and top it up to WANT. 1280 The padding goes on the section that actually PAINTS the background, found by 1281 hit-testing the top of the viewport. Padding sits inside that element's own 1282 background box, so the hero still starts at y=0 and no bare strip appears. 1283 Targeting the section rather than the first heading matters: on the homepage a 1284 block sits above the title, so padding the heading moved the wrong thing. 1285 WANT is the one knob - 58 read as slightly tight, 96 as too generous. */ 1286if(!matchMedia('(max-width:767px)').matches)return; 1287var WANT=64,done=false; 1288 1289function paints(el){var s=getComputedStyle(el),b=s.backgroundColor; 1290return(b&&b!=='transparent'&&b!=='rgba(0, 0, 0, 0)')|| 1291(s.backgroundImage&&s.backgroundImage!=='none')} 1292 1293/* topmost thing under the very top of the screen, walked up to the section that 1294 paints. Falls back to the outermost full-width block if nothing paints. */ 1295function hero(){ 1296var el=null; 1297try{el=document.elementFromPoint(Math.round(innerWidth/2),4)}catch(e){} 1298if(!el)return null; 1299var fb=null; 1300while(el&&el!==document.body&&el!==document.documentElement){ 1301var r=el.getBoundingClientRect(); 1302if(r.top<=2&&r.height>80){if(!fb)fb=el;if(paints(el))return el} 1303el=el.parentElement} 1304return fb} 1305 1306function fix(){ 1307if(done)return; 1308var nv=document.getElementById('nv-root');if(!nv)return; 1309var pill=nv.querySelector('.nv-wrap')||nv; 1310var pb=pill.getBoundingClientRect().bottom; 1311if(!pb)return; 1312var s=hero();if(!s)return; 1313var pad=parseFloat(getComputedStyle(s).paddingTop)||0; 1314var contentTop=s.getBoundingClientRect().top+pad; 1315var add=WANT-(contentTop-pb); 1316/* only ever top up, and never by an absurd amount if the measurement is odd */ 1317if(add>0&&add<260){s.style.paddingTop=(pad+add)+'px';done=true} 1318else if(add<=0)done=true} 1319 1320if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',fix); 1321else fix(); 1322addEventListener('load',fix); 1323})(); 1324</script>
1324<style> 1325/*width*/ 1326::-webkit-scrollbar { 1327width:0px; 1328} 1329</style>
1329<script src="https://cdn.prod.website-files.com/679d63c04202ad9bf67ca282%2F689e5ba67671442434f3ca35%2F6a75eb30603da83acd2b4c30%2Faxassist2-1.0.0.js" type="text/javascript"></script>
1329</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.