(function(){ var API=document.currentScript?.src?.replace('/widget.js','')||''; var convId=null; var open=false; var loading=false; var css=document.createElement('style'); css.textContent=` .ll-chat-bubble{position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;background:#8E5574;cursor:pointer;box-shadow:0 4px 20px rgba(142,85,116,.3);display:flex;align-items:center;justify-content:center;z-index:99999;transition:transform .2s,box-shadow .2s;border:0} .ll-chat-bubble:hover{transform:scale(1.08);box-shadow:0 6px 28px rgba(142,85,116,.4)} .ll-chat-bubble svg{width:24px;height:24px;fill:#fff} .ll-chat-panel{position:fixed;bottom:92px;right:24px;width:380px;max-width:calc(100vw - 32px);height:520px;max-height:calc(100vh - 120px);background:#fff;border-radius:20px;box-shadow:0 12px 48px rgba(43,66,63,.18);z-index:99999;display:none;flex-direction:column;overflow:hidden;font-family:'Poppins',sans-serif} .ll-chat-panel.open{display:flex} .ll-chat-hd{background:#2B423F;padding:16px 20px;display:flex;align-items:center;justify-content:space-between} .ll-chat-title{color:#fff;font-size:14px;font-weight:600;font-family:'Space Grotesk',sans-serif;letter-spacing:.5px} .ll-chat-close{background:none;border:none;color:rgba(255,255,255,.6);cursor:pointer;font-size:20px;padding:0 4px;line-height:1} .ll-chat-close:hover{color:#fff} .ll-chat-msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px} .ll-chat-msg{max-width:85%;padding:10px 14px;border-radius:16px;font-size:13px;line-height:1.6;word-wrap:break-word} .ll-chat-msg.bot{background:#F7F8F5;color:#2B423F;align-self:flex-start;border-bottom-left-radius:4px} .ll-chat-msg.user{background:#2B423F;color:#fff;align-self:flex-end;border-bottom-right-radius:4px} .ll-chat-msg.error{background:#F3E8EE;color:#8E5574;align-self:flex-start;border-bottom-left-radius:4px;font-size:12px} .ll-chat-msg.typing{background:#F7F8F5;color:#9CA383;align-self:flex-start;border-bottom-left-radius:4px;font-style:italic} .ll-chat-input-wrap{padding:12px 16px;border-top:1px solid #E8EBD9;display:flex;gap:8px;align-items:flex-end} .ll-chat-input{flex:1;border:1px solid #E8EBD9;border-radius:12px;padding:10px 14px;font-size:13px;font-family:'Poppins',sans-serif;resize:none;outline:none;max-height:80px;line-height:1.4;color:#2B423F} .ll-chat-input:focus{border-color:#9CA383} .ll-chat-input::placeholder{color:#999} .ll-chat-send{width:36px;height:36px;border-radius:50%;background:#2B423F;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .2s} .ll-chat-send:hover{background:#365250} .ll-chat-send:disabled{background:#ccc;cursor:not-allowed} .ll-chat-send svg{width:16px;height:16px;fill:#fff} .ll-chat-upload{width:36px;height:36px;border-radius:50%;background:transparent;border:1px solid #E8EBD9;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:border-color .2s} .ll-chat-upload:hover{border-color:#9CA383} .ll-chat-upload svg{width:16px;height:16px;fill:#9CA383} .ll-chat-img-preview{padding:8px 16px 0;display:none} .ll-chat-img-preview img{max-height:80px;border-radius:8px;border:1px solid #E8EBD9} .ll-chat-img-preview .ll-remove{font-size:10px;color:#8E5574;cursor:pointer;margin-top:4px;display:inline-block} .ll-chat-welcome{text-align:center;padding:32px 24px;color:#365250;font-size:13px;line-height:1.7} .ll-chat-welcome strong{color:#2B423F;display:block;font-size:16px;margin-bottom:8px;font-family:'Space Grotesk',sans-serif} .ll-chat-limit{text-align:center;font-size:11px;color:#999;padding:4px 16px 8px} @media(max-width:480px){.ll-chat-panel{bottom:0;right:0;width:100%;max-width:100%;height:100vh;max-height:100vh;border-radius:0}.ll-chat-bubble{bottom:16px;right:16px}} `; document.head.appendChild(css); // Bubble var bubble=document.createElement('button'); bubble.className='ll-chat-bubble'; bubble.setAttribute('aria-label','Chat with us'); bubble.innerHTML=''; document.body.appendChild(bubble); // Panel var panel=document.createElement('div'); panel.className='ll-chat-panel'; panel.innerHTML=`
Ask L&L
Hey! How can we help? Ask us anything about our workbooks, which one is right for you, or get help if you are stuck on a section.
Preview remove
`; document.body.appendChild(panel); var msgs=document.getElementById('ll-msgs'); var input=document.getElementById('ll-input'); var sendBtn=document.getElementById('ll-send'); var closeBtn=panel.querySelector('.ll-chat-close'); var uploadBtn=document.getElementById('ll-upload-btn'); var fileInput=document.getElementById('ll-file-input'); var imgPreview=document.getElementById('ll-img-preview'); var imgThumb=document.getElementById('ll-img-thumb'); var imgRemove=document.getElementById('ll-img-remove'); var limitEl=document.getElementById('ll-limit'); var pendingImage=null; // Try restore session try{ var s=localStorage.getItem('ll-chat-session'); if(s){var d=JSON.parse(s);convId=d.conversationId||null} }catch(e){} bubble.addEventListener('click',function(){ open=!open; panel.classList.toggle('open',open); if(open)input.focus(); }); closeBtn.addEventListener('click',function(){ open=false; panel.classList.remove('open'); }); uploadBtn.addEventListener('click',function(){fileInput.click()}); fileInput.addEventListener('change',function(){ var file=fileInput.files[0]; if(!file)return; if(file.size>5*1024*1024){addMsg('error','Image must be under 5MB.');return} var reader=new FileReader(); reader.onload=function(){ pendingImage={file:file,dataUrl:reader.result}; imgThumb.src=reader.result; imgPreview.style.display='block'; }; reader.readAsDataURL(file); }); imgRemove.addEventListener('click',function(){ pendingImage=null; imgPreview.style.display='none'; fileInput.value=''; }); function addMsg(type,text){ var welcome=msgs.querySelector('.ll-chat-welcome'); if(welcome)welcome.remove(); var div=document.createElement('div'); div.className='ll-chat-msg '+type; div.textContent=text; msgs.appendChild(div); msgs.scrollTop=msgs.scrollHeight; return div; } function updateLimit(remaining){ if(!remaining)return; var m=remaining.messagesInConversation; if(m<=5){limitEl.textContent=m+' messages remaining in this conversation'} else{limitEl.textContent=''} } async function send(){ var text=input.value.trim(); if(!text&&!pendingImage)return; if(loading)return; loading=true; sendBtn.disabled=true; if(text)addMsg('user',text); if(pendingImage)addMsg('user','[Image attached]'); input.value=''; input.style.height='auto'; var typingEl=addMsg('typing','Thinking...'); try{ var fd; if(pendingImage){ fd=new FormData(); fd.append('message',text); fd.append('image',pendingImage.file); if(convId)fd.append('conversationId',convId); } var resp=await fetch(API+'/api/chat',{ method:'POST', headers:pendingImage?{}:{'Content-Type':'application/json'}, body:pendingImage?fd:JSON.stringify({message:text,conversationId:convId}) }); pendingImage=null; imgPreview.style.display='none'; fileInput.value=''; var data=await resp.json(); typingEl.remove(); if(data.error==='rate_limit'){ addMsg('error',data.message); return; } if(data.error){ addMsg('error',data.error); return; } convId=data.conversationId; try{localStorage.setItem('ll-chat-session',JSON.stringify({conversationId:convId}))}catch(e){} addMsg('bot',data.message); updateLimit(data.remaining); }catch(err){ typingEl.remove(); addMsg('error','Something went wrong. Please try again or email hello@latteandlaunch.com.'); }finally{ loading=false; sendBtn.disabled=false; input.focus(); } } sendBtn.addEventListener('click',send); input.addEventListener('keydown',function(e){ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()} }); input.addEventListener('input',function(){ this.style.height='auto'; this.style.height=Math.min(this.scrollHeight,80)+'px'; }); })();