(function () { 'use strict'; /** Auto resize helper part 1 */ let lastSentHeight = 0; function notifyParentHeight() { const widget = document.getElementById("hc-widget"); if (!widget) return; const height = widget.offsetHeight; // Only send if meaningfully different if (Math.abs(height - lastSentHeight) > 2) { lastSentHeight = height; window.parent.postMessage({ type: "hc-resize", height: height + 4 }, "*"); } } function notifyParentHeightSmooth(duration = 600) { const start = performance.now(); function tick(now) { notifyParentHeight(); if (now - start < duration) { requestAnimationFrame(tick); } } requestAnimationFrame(tick); } /** End auto resize helper part 1 */ // ── Tab content ──────────────────────────────────────────────────────── var TABS = [ { label: 'Stay on top', desc: 'Nothing slips through — Lunos manages invoices, chases contacts, handles every follow-up, and escalates when it matters.' }, { label: 'Effortless follow-up', desc: 'Lunos handles client replies and follow-ups at whatever level of automation suits you — so nothing falls through the cracks.' }, { label: 'Smart reporting', desc: 'See the full picture at a glance. Real-time dashboards show DSO, aging, collection rates and trends — no spreadsheets required.' }, { label: 'Set the tone', desc: 'Make every message sound like you. Blend tones to set the right voice for your payment communications — formal, friendly, or anywhere in between.' } ]; // ── Element refs ─────────────────────────────────────────────────────── var widget = document.getElementById('hc-widget'); var descEl = document.getElementById('hc-desc'); var tabBtns = Array.from(widget.querySelectorAll('.hc-tab')); var panels = Array.from(widget.querySelectorAll('.hc-panel')); var panelsEl = widget.querySelector('.hc-panels'); // ── State ────────────────────────────────────────────────────────────── var current = 0; var splitInst = null; var animating = false; var chatTl = null; var chatSplits = null; var tdTl = null; // ── Helpers ──────────────────────────────────────────────────────────── /** Revert any previous SplitText instance and remove wrapper divs */ function clearSplit() { if (splitInst) { splitInst.revert(); splitInst = null; } descEl.querySelectorAll('.hc-line-wrap').forEach(function (w) { while (w.firstChild) { w.parentNode.insertBefore(w.firstChild, w); } w.remove(); }); } /** Animate description text using SplitText word reveal */ function animateDesc(text) { clearSplit(); descEl.textContent = text; // Graceful fallback if SplitText hasn't loaded if (typeof SplitText === 'undefined') { gsap.fromTo(descEl, { opacity: 0, y: 8 }, { opacity: 1, y: 0, duration: 0.4, ease: 'power3.out' }); return; } splitInst = new SplitText(descEl, { type: 'lines,words', linesClass: 'hc-split-line' }); // Wrap each line to clip upward-travelling words splitInst.lines.forEach(function (line) { var wrap = document.createElement('div'); wrap.className = 'hc-line-wrap'; line.parentNode.insertBefore(wrap, line); wrap.appendChild(line); }); gsap.set(splitInst.words, { y: 0, opacity: 0 }); gsap.to(splitInst.words, { y: 0, opacity: 1, duration: 0.48, stagger: 0.038, ease: 'power3.out', overwrite: true }); } // ── Reporting table animation ─────────────────────────────────────────── function startReportingAnimation() { var panel2 = document.getElementById('hc-panel-2'); if (!panel2) return; panel2.classList.remove('rt-animate'); void panel2.offsetWidth; // force reflow so removing the class takes effect panel2.classList.add('rt-animate'); } // ── Communications chat animation ────────────────────────────────────── function revertChatSplits() { if (chatSplits) { chatSplits.forEach(function (sp) { if (sp) sp.revert(); }); chatSplits = null; } } function startChatAnimation() { if (chatTl) { chatTl.kill(); chatTl = null; } revertChatSplits(); var vp = document.getElementById('hc-comm-viewport'); if (!vp) return; var vpH = vp.offsetHeight; var GAP = 12; var PAD = window.innerWidth <= 479 ? 130 : 150; var items = Array.from(vp.querySelectorAll('.hc-chat-item')); gsap.set(items, { y: vpH + 60, opacity: 0 }); var heights = items.map(function (el) { return el.offsetHeight; }); function restY(h) { return vpH - PAD - h; } var splits = items.map(function (item) { if (item.getAttribute('data-chat') !== 'lunos') return null; var el = item.querySelector('.hc-chat-text'); if (!el || typeof SplitText === 'undefined') return null; var sp = new SplitText(el, { type: 'words,chars', charsClass: 'hc-split-char', wordsClass: 'hc-split-word' }); gsap.set(sp.chars, { opacity: 0 }); return sp; }); chatSplits = splits; var PUSH = 0.38; // existing items slide up var SLIDE = 0.42; // new item slides in var FADE = 0.42; // customer / pill fade-in var TYPE = 1.9; // total typing duration per Lunos message var WAIT = 1.2; // reading pause after customer / pill var WAIT_T = 1.7; // reading pause after Lunos typing var tl = gsap.timeline({ paused: true }); var prevDoneAt = 0; items.forEach(function (item, i) { var h = heights[i]; var sp = splits[i]; var pushAt; if (i === 0) { pushAt = 0; } else { var prevChat = items[i - 1].getAttribute('data-chat'); var wait = (prevChat === 'lunos') ? WAIT_T : WAIT; pushAt = prevDoneAt + wait; } var slideAt = i > 0 ? pushAt + PUSH : pushAt; var revealAt = i === 0 ? slideAt : slideAt + SLIDE; if (i > 0) { tl.to(items.slice(0, i), { y: '-=' + (h + GAP), duration: PUSH, ease: 'power3.inOut', stagger: 0 }, pushAt); } tl.to(item, { y: restY(h), duration: SLIDE, ease: 'power3.out' }, slideAt); if (sp) { tl.to(item, { opacity: 1, duration: 0.22, ease: 'power3.out' }, slideAt); tl.to(sp.chars, { opacity: 1, duration: 0.01, stagger: { amount: TYPE, ease: 'none' }, ease: 'none' }, revealAt); prevDoneAt = revealAt + TYPE; } else { tl.to(item, { opacity: 1, duration: FADE, ease: 'power3.out' }, revealAt); prevDoneAt = revealAt + FADE; } }); tl.to(items, { opacity: 0, duration: 0.5, ease: 'power3.in', stagger: 0 }, prevDoneAt + 1.2); tl.eventCallback('onComplete', function () { revertChatSplits(); if (current === 1) { startChatAnimation(); } }); chatTl = tl; tl.play(); } // ── To-do animation ──────────────────────────────────────────────────── function autoApproveRow(tl, row, at, todoCountEl, schedCountEl, todoVal, schedVal) { var btns = row.querySelector('.td-btns'); var badge = row.querySelector('.td-auto-approve'); var textEl = badge ? badge.querySelector('.td-aa-text') : null; var checkPath = badge ? badge.querySelector('.td-aa-check path') : null; var chars = badge ? Array.from(badge.querySelectorAll('.td-aa-char')) : []; var checkSvg = badge ? badge.querySelector('.td-aa-check') : null; var CHAR_DUR = 0.1; var CHAR_GAP = 0.025; // Buttons (or action cell on xs) fade out var isXS = window.innerWidth <= 479; var fadeEl = isXS ? row.querySelector('.td-action-cell') : btns; tl.to(fadeEl, { opacity: 0, duration: 0.2, ease: 'power2.in' }, at); // Badge container fades in tl.to(badge, { opacity: 1, duration: 0.15, ease: 'power2.out' }, at + 0.18); // Each character fades in sequentially chars.forEach(function (char, i) { tl.to(char, { opacity: 1, duration: CHAR_DUR, ease: 'power2.out' }, at + 0.3 + i * CHAR_GAP); }); // Checkmark draws after last character fades in var typeEnd = at + 0.3 + (chars.length - 1) * CHAR_GAP + CHAR_DUR; if (checkSvg) { tl.fromTo(checkSvg, { opacity: 0, scale: 0.6 }, { opacity: 1, scale: 1, duration: 0.3, ease: 'back.out(1.8)' }, typeEnd + 0.01); } if (checkPath) { tl.to(checkPath, { strokeDashoffset: 0, duration: 0.36, ease: 'power2.out' }, typeEnd + 0.01); } // Row fades up and collapses after tick drawn var rowFadeAt = typeEnd + 0.01 + 0.36 + 0.18; tl.to(row, { opacity: 0, y: -14, duration: 0.28, ease: 'power2.in' }, rowFadeAt); tl.to(row, { height: 0, paddingTop: 0, paddingBottom: 0, borderBottomWidth: 0, duration: 0.28, ease: 'power2.inOut' }, rowFadeAt + 0.18); (function (tv, sv) { tl.call(function () { todoCountEl.textContent = tv; schedCountEl.textContent = sv; gsap.fromTo(todoCountEl, { scale: 1.3 }, { scale: 1, duration: 0.22, ease: 'back.out(2)' }); gsap.fromTo(schedCountEl, { scale: 1.3 }, { scale: 1, duration: 0.22, ease: 'back.out(2)' }); }, null, rowFadeAt + 0.24); }(todoVal, schedVal)); } function startTodoAnimation() { if (tdTl) { tdTl.kill(); tdTl = null; } var rows = Array.from(document.querySelectorAll('.td-row')); var emptyEl = document.getElementById('td-empty'); var todoCountEl = document.getElementById('td-todo-count'); var schedCountEl= document.getElementById('td-sched-count'); var chatPanel = document.getElementById('td-chat-panel'); var chatInner = document.getElementById('td-chat-inner'); var chatBtn = document.getElementById('td-chat-btn'); var chatClose = document.getElementById('td-chat-close'); var sendBtn = document.getElementById('td-chat-send-btn'); var inputEl = document.getElementById('td-chat-input-text'); var userBubble = document.getElementById('td-chat-user-msg'); var botBubble = document.getElementById('td-chat-bot-reply'); var userBubble2 = document.getElementById('td-chat-user-msg-2'); if (!emptyEl || rows.length === 0) return; // Reset all rows and counters gsap.set(rows, { clearProps: 'all' }); rows.forEach(function (r) { r.style.height = ''; r.style.display = ''; var btns = r.querySelector('.td-btns'); var badge = r.querySelector('.td-auto-approve'); var chars = badge ? Array.from(badge.querySelectorAll('.td-aa-char')) : []; var checkPath = badge ? badge.querySelector('.td-aa-check path') : null; var actionCell = r.querySelector('.td-action-cell'); if (btns) gsap.set(btns, { clearProps: 'opacity' }); if (actionCell) gsap.set(actionCell, { clearProps: 'opacity' }); if (badge) gsap.set(badge, { opacity: 0 }); if (chars.length) gsap.set(chars, { opacity: 0 }); if (checkPath) gsap.set(checkPath, { strokeDashoffset: 1 }); }); gsap.set(emptyEl, { opacity: 0 }); // Reset chat state gsap.set(chatPanel, { width: 0 }); if (chatInner) gsap.set(chatInner, { opacity: 0 }); if (chatBtn) gsap.set(chatBtn, { opacity: 1, clearProps: 'scale' }); if (inputEl) inputEl.textContent = ''; if (userBubble) { userBubble.textContent = ''; gsap.set(userBubble, { opacity: 0 }); } if (botBubble) { botBubble.textContent = ''; gsap.set(botBubble, { opacity: 0 }); } if (userBubble2) { userBubble2.textContent = ''; gsap.set(userBubble2, { opacity: 0 }); } todoCountEl.textContent = '5'; schedCountEl.textContent = '0'; var tl = gsap.timeline(); var READ = 1.6; var GAP = 1.6; // longer gap to accommodate auto-approve animation // ── Auto-approve rows 0 and 1 (Harber, Weissnat) ────────── autoApproveRow(tl, rows[0], READ, todoCountEl, schedCountEl, '4', '1'); autoApproveRow(tl, rows[1], READ + GAP, todoCountEl, schedCountEl, '3', '2'); // ── Chat sequence (opens before Dylan's row is approved) ── var CHAT_OPEN = READ + GAP + 2.0; // after row 1 fully collapses // Simulate clicking the chat button — grow then spring-press tl.to(chatBtn, { scale: 1.2, duration: 0.16, ease: 'power2.out' }, CHAT_OPEN - 0.26); tl.to(chatBtn, { scale: 0.86, duration: 0.1, ease: 'power2.in' }, CHAT_OPEN - 0.1); tl.to(chatBtn, { scale: 1, duration: 0.22, ease: 'back.out(2.5)' }, CHAT_OPEN); // Button fades out as panel opens tl.to(chatBtn, { opacity: 0, duration: 0.22, ease: 'power2.in' }, CHAT_OPEN + 0.04); // Chat panel slides in (pushes all content left — tabs + list) var chatExpandWidth = window.innerWidth <= 479 ? chatPanel.parentElement.offsetWidth : 320; tl.to(chatPanel, { width: chatExpandWidth, duration: 0.45, ease: 'power3.out' }, CHAT_OPEN + 0.08); // Chat inner content fades in after panel is fully open tl.to(chatInner, { opacity: 1, duration: 0.25, ease: 'power2.out' }, CHAT_OPEN + 0.08 + 0.45); // User types question var userMsg = 'Why are we escalating?'; var TYPE_START = CHAT_OPEN + 0.08 + 0.45 + 0.25 + 0.7; // pause to read greeting for (var c = 0; c < userMsg.length; c++) { (function (idx) { tl.call(function () { inputEl.textContent = userMsg.slice(0, idx + 1); }, null, TYPE_START + idx * 0.044); }(c)); } // Send: clear input, animate send button, show user bubble var SEND_AT = TYPE_START + userMsg.length * 0.044 + 0.28; tl.to(sendBtn, { scale: 0.82, duration: 0.08 }, SEND_AT - 0.08); tl.to(sendBtn, { scale: 1, duration: 0.18, ease: 'back.out(2)' }, SEND_AT); tl.call(function () { inputEl.textContent = ''; userBubble.textContent = userMsg; }, null, SEND_AT); tl.fromTo(userBubble, { opacity: 0, y: 6 }, { opacity: 1, y: 0, duration: 0.3, ease: 'power2.out' }, SEND_AT + 0.05); // Bot reply types out character by character var botReply = 'Dylan Soffstik has an outstanding balance of $12,450 and no answer from contact. Would you like to review or approve?'; var BOT_START = SEND_AT + 0.75; tl.set(botBubble, { opacity: 1 }, BOT_START); for (var c2 = 0; c2 < botReply.length; c2++) { (function (idx) { tl.call(function () { botBubble.textContent = botReply.slice(0, idx + 1); }, null, BOT_START + idx * 0.022); }(c2)); } var BOT_END = BOT_START + botReply.length * 0.022; // User reads reply, types second message var userMsg2 = 'You can approve - no need to ask next time.'; var TYPE_START2 = BOT_END + 1.6; for (var c3 = 0; c3 < userMsg2.length; c3++) { (function (idx) { tl.call(function () { inputEl.textContent = userMsg2.slice(0, idx + 1); }, null, TYPE_START2 + idx * 0.044); }(c3)); } // Send second message var SEND_AT2 = TYPE_START2 + userMsg2.length * 0.044 + 0.28; tl.to(sendBtn, { scale: 0.82, duration: 0.08 }, SEND_AT2 - 0.08); tl.to(sendBtn, { scale: 1, duration: 0.18, ease: 'back.out(2)' }, SEND_AT2); tl.call(function () { inputEl.textContent = ''; userBubble2.textContent = userMsg2; }, null, SEND_AT2); tl.fromTo(userBubble2, { opacity: 0, y: 6 }, { opacity: 1, y: 0, duration: 0.3, ease: 'power2.out' }, SEND_AT2 + 0.05); // User sent message, then closes the chat var CLOSE_AT = SEND_AT2 + 1.4; tl.to(chatClose, { scale: 0.82, duration: 0.09 }, CLOSE_AT - 0.09); tl.to(chatClose, { scale: 1, duration: 0.18, ease: 'back.out(2)' }, CLOSE_AT); // Fade inner content out first, then collapse panel, then restore chat button tl.to(chatInner, { opacity: 0, duration: 0.22, ease: 'power2.in' }, CLOSE_AT + 0.1); tl.to(chatPanel, { width: 0, duration: 0.4, ease: 'power3.inOut' }, CLOSE_AT + 0.36); tl.to(chatBtn, { opacity: 1, duration: 0.28, ease: 'power2.out' }, CLOSE_AT + 0.62); var CHAT_CLOSED = CLOSE_AT + 0.78; // ── Auto-approve rows 2, 3, 4 (Dylan, Red Rock, Chen) ────── var POST_GAP = 0.5; autoApproveRow(tl, rows[2], CHAT_CLOSED + POST_GAP, todoCountEl, schedCountEl, '2', '3'); autoApproveRow(tl, rows[3], CHAT_CLOSED + POST_GAP + GAP, todoCountEl, schedCountEl, '1', '4'); autoApproveRow(tl, rows[4], CHAT_CLOSED + POST_GAP + GAP * 2, todoCountEl, schedCountEl, '0', '5'); var doneAt = CHAT_CLOSED + POST_GAP + GAP * 2 + 1.9; // Fade in empty state, hold, loop tl.to(emptyEl, { opacity: 1, duration: 0.55, ease: 'power2.out' }, doneAt); tl.to(emptyEl, { opacity: 0, duration: 0.45, ease: 'power2.in' }, doneAt + 3.2); tl.call(function () { startTodoAnimation(); }, null, doneAt + 3.8); tdTl = tl; tl.play(); } /** Update tab state, cross-fade panels, then kick off panel-specific animations */ function goToTab(next) { if (next === current) return; var prev = current; current = next; // Pause to-do animation when leaving tab 0 if (prev === 0 && tdTl) { tdTl.pause(); } // Pause chat when leaving Communications and clean up any live SplitText nodes if (prev === 1 && chatTl) { chatTl.pause(); revertChatSplits(); } // Deactivate old tab button tabBtns[prev].classList.remove('hc-active'); tabBtns[prev].setAttribute('aria-selected', 'false'); // Activate new tab button tabBtns[current].classList.add('hc-active'); tabBtns[current].setAttribute('aria-selected', 'true'); animateDesc(TABS[current].desc); animating = true; var outgoing = panels[prev]; var incoming = panels[next]; gsap.to(outgoing, { opacity: 0, y: 6, duration: 0.22, ease: 'power2.in', onComplete: function () { // Reset reporting cells only after outgoing panel is gone — prevents // cells snapping to opacity:0 while still visible during the fade if (prev === 2) { document.getElementById('hc-panel-2').classList.remove('rt-animate'); } outgoing.classList.remove('hc-active'); gsap.set(outgoing, { clearProps: 'y,opacity' }); // Update container height after outgoing fades to avoid layout shift panelsEl.classList.toggle('hc-p2-active', next === 2); panelsEl.classList.toggle('hc-p3-active', next === 3); // On mobile, fit panel 2 height to actual table content; desktop stays fixed if (next === 2 && window.innerWidth <= 1260) { var tbl = document.getElementById('hc-panel-2').querySelector('table'); if (tbl) panelsEl.style.height = tbl.offsetHeight + 'px'; } else { panelsEl.style.height = ''; } incoming.classList.add('hc-active'); gsap.fromTo(incoming, { opacity: 0, y: 6 }, { opacity: 1, y: 0, duration: 0.36, ease: 'power3.out', onComplete: function () { // Clear GSAP inline transform/opacity so CSS (e.g. translateX(-50%)) retakes control gsap.set(incoming, { clearProps: 'transform,opacity' }); animating = false; // Kick off panel animations precisely when the panel is fully visible if (next === 0) { startTodoAnimation(); } if (next === 1) { startChatAnimation(); } if (next === 2) { startReportingAnimation(); } if (next === 3 && window._btPulseUnselected) { window._btPulseUnselected(); } /* auto resize helper part 2*/ notifyParentHeightSmooth(); /* end auto resize helper part 2*/ } } ); } }); } // ── Event listeners ──────────────────────────────────────────────────── tabBtns.forEach(function (btn, i) { btn.addEventListener('click', function () { if (i === current) return; goToTab(i); }); }); // ── Resize: clear stale GSAP inline transforms so CSS media queries win ── var resizeTimer; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { panels.forEach(function (p) { gsap.set(p, { clearProps: 'transform,opacity' }); }); /*end auto resize-helper part 4*/ notifyParentHeightSmooth(); /*end auto resize-helper part 4*/ }, 150); }); // ── Customisation: brand tone interactive ─────────────────────────────── (function () { var btColors = [ { dot: '#FE42A0', hlBg: 'rgba(254,66,160,0.09)', hlShadow: 'inset 2px 0 0 #FE42A0' }, { dot: '#4EA8DE', hlBg: 'rgba(141,200,255,0.18)', hlShadow: 'inset 2px 0 0 #8DC8FF' }, { dot: '#4CD571', hlBg: 'rgba(76,213,113,0.13)', hlShadow: 'inset 2px 0 0 #4CD571' }, ]; var btSlotPriority = { formal: ['closing', 'subject'], direct: ['subject', 'body'], calm: ['body', 'closing'], empathetic: ['greeting', 'body'], friendly: ['greeting', 'closing'], casual: ['body', 'greeting'], fun: ['subject', 'closing'], playful: ['closing', 'greeting'], }; var btNeutral = { subject: 'Payment Reminder: Outstanding Balance', greeting: 'Hi ABC Company team,', body: 'There is an outstanding balance of $5,000.00 on your account. A payment of $1,200.00 is 15 days overdue. Please follow the links below to view details and make payment. If you have already made payment or have a query, please reply to this email. We will follow up through other channels if we don\'t hear from you.', closing: 'Regards, Lunos', }; var btContent = { formal: { subject: 'Payment Reminder: Outstanding Balance', greeting: 'Dear ABC Company,', body: 'We are writing to inform you of an outstanding balance of $5,000.00 on your account. A payment of $1,200.00 is currently 15 days overdue. Please follow the links below to review your account and submit payment at your earliest convenience. Should you have already remitted payment or wish to discuss this matter, please reply to this email directly.', closing: 'Yours sincerely, Lunos', }, direct: { subject: 'Action Required: $1,200.00 Overdue', greeting: 'Hi ABC Company,', body: 'Your account has $5,000.00 outstanding. $1,200.00 is 15 days overdue. Click the links below to pay. Already paid? Reply to this email. We\'ll follow up shortly.', closing: 'Lunos', }, calm: { subject: 'A Gentle Reminder About Your Account', greeting: 'Hi ABC Company team,', body: 'We understand there\'s an outstanding balance of $5,000.00 on your account, with $1,200.00 now 15 days overdue. We\'re here to help if you have any questions. Please follow the links below to view details and make payment. Feel free to reply if you\'ve already paid or have a query.', closing: 'Reach out anytime, Lunos', }, empathetic: { subject: 'We\'re Here to Help — Account Update', greeting: 'Hi there, ABC Company team,', body: 'We know managing finances can be challenging, and we genuinely want to support you. There\'s an outstanding balance of $5,000.00 on your account, with $1,200.00 now 15 days overdue. Please follow the links below to view your account — we\'d love to find a solution that works for you.', closing: 'We\'re with you every step of the way, Lunos', }, friendly: { subject: 'A Quick Note About Your Balance!', greeting: 'Hi ABC Company team!', body: 'Just a heads up — there\'s an outstanding balance of $5,000.00 on your account, and $1,200.00 is 15 days overdue. No worries, just follow the links below to take a look and make payment. Already sorted it? Brilliant, just reply and let us know!', closing: 'Thanks so much! Lunos', }, casual: { subject: 'Heads up on your account', greeting: 'Hey ABC Company,', body: 'Just dropping you a line — there\'s $5,000.00 outstanding on your account and $1,200.00 that\'s been overdue for about 15 days. If you could sort payment via the links below, that\'d be great. Already paid? Just shoot us a reply.', closing: 'Cheers, Lunos', }, fun: { subject: '👋 Your balance needs some attention!', greeting: 'Hey ABC Company! 👋', body: 'Guess what — your account has $5,000.00 outstanding, and $1,200.00 has been waiting patiently for 15 days! 😄 No stress — just click the links below to settle up. Already paid? You\'re a star ⭐ — just reply and let us know!', closing: 'High fives, Lunos 🚀', }, playful: { subject: 'A little nudge from Lunos', greeting: 'Hello ABC Company team,', body: 'We\'re giving you a little nudge! There\'s $5,000.00 outstanding on your account, with $1,200.00 just a touch overdue at 15 days. Whenever you\'re ready, the links below will take you straight to payment — easy as pie. Already taken care of it? Wonderful, just drop us a reply!', closing: 'Warmly, Lunos', }, }; var btSelected = ['formal']; var btAnimating = false; var btPulsed = false; function btAssignSlots() { var assigned = {}; btSelected.forEach(function (tone, i) { btSlotPriority[tone].forEach(function (slot) { if (!assigned[slot]) { assigned[slot] = { tone: tone, colorIndex: i }; } }); }); return assigned; } function btRenderEmail() { var assigned = btAssignSlots(); ['subject', 'greeting', 'body', 'closing'].forEach(function (slot) { var el = document.getElementById('hc-bt-' + slot); var hit = assigned[slot]; if (hit) { var c = btColors[hit.colorIndex]; el.textContent = btContent[hit.tone][slot]; el.classList.add('hc-bt-hl'); el.style.background = c.hlBg; el.style.boxShadow = c.hlShadow; } else { el.textContent = btNeutral[slot]; el.classList.remove('hc-bt-hl'); el.style.background = ''; el.style.boxShadow = ''; } }); } function btRenderPills() { document.querySelectorAll('.hc-bt-pill').forEach(function (pill) { var tone = pill.dataset.btone; var idx = btSelected.indexOf(tone); var dot = pill.querySelector('.hc-bt-pill-dot'); if (idx >= 0) { pill.classList.add('hc-bt-active'); dot.style.background = btColors[idx].dot; } else { pill.classList.remove('hc-bt-active'); dot.style.background = ''; } }); } function btUpdate(animate) { btRenderPills(); if (!animate || btAnimating) { btRenderEmail(); return; } btAnimating = true; var emailEl = document.getElementById('hc-bt-email'); emailEl.classList.add('hc-bt-out'); setTimeout(function () { btRenderEmail(); emailEl.classList.remove('hc-bt-out'); btAnimating = false; }, 180); } document.querySelectorAll('.hc-bt-pill').forEach(function (pill) { pill.addEventListener('click', function () { var tone = pill.dataset.btone; var idx = btSelected.indexOf(tone); if (idx >= 0) { btSelected.splice(idx, 1); } else { if (btSelected.length >= 3) { return; } btSelected.push(tone); } btUpdate(true); }); }); function btPulseUnselected() { if (btPulsed) { return; } btPulsed = true; var unselected = Array.from(document.querySelectorAll('.hc-bt-pill')).filter(function (p) { return !p.classList.contains('hc-bt-active'); }); unselected.forEach(function (pill, i) { setTimeout(function () { pill.classList.remove('hc-bt-pulse'); void pill.offsetWidth; // force reflow to restart animation pill.classList.add('hc-bt-pulse'); }, i * 60); }); } btUpdate(false); window._btPulseUnselected = btPulseUnselected; })(); // ── Init ─────────────────────────────────────────────────────────────── // Short delay ensures GSAP + SplitText are fully parsed before first run setTimeout(function () { animateDesc(TABS[0].desc); startTodoAnimation(); /*auto resize-helper part 3*/ notifyParentHeightSmooth(); /*end auto resize-helper part 3*/ }, 120); })();