Drawer menu
*
* AVATAR: Upload your character image to Shopify Admin โ Content โ Files,
* then replace AVATAR_URL below with the CDN link.
*/
(function () {
'use strict';
// โโโ Config โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var WEBHOOK_URL = 'https://twolf312.app.n8n.cloud/webhook/tw-product-assistant';
var BRAND_GREEN = '#157e4a';
var BRAND_GREEN_DARK = '#0e5c36';
var BRAND_CREAM = '#f8f4ed';
var WIDGET_ID = 'tw-ai-chat';
var AVATAR_URL = 'https://cdn.shopify.com/s/files/1/0696/9265/3873/files/timberwolf-avatar.png?v=1773440244'; // โ paste Shopify CDN image URL here once uploaded
// Prevent double-init
if (document.getElementById(WIDGET_ID + '-btn')) return;
// โโโ Session persistence (survives page navigation, clears on tab close) โโโ
var SESSION_KEY = 'tw-chat-session';
var session = JSON.parse(sessionStorage.getItem(SESSION_KEY) || 'null');
if (!session) {
session = {
id: 'tw-' + Date.now() + '-' + Math.random().toString(36).substring(2, 8),
history: []
};
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
}
var conversationId = session.id;
// โโโ Inject styles โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var style = document.createElement('style');
style.textContent = [
'#tw-ai-chat-btn {',
' position: fixed; bottom: 24px; right: 24px; z-index: 99998;',
' width: 60px; height: 60px; border-radius: 50%;',
' background: ' + BRAND_GREEN + '; border: none; cursor: pointer;',
' box-shadow: 0 4px 16px rgba(0,0,0,0.25);',
' display: flex; align-items: center; justify-content: center;',
' transition: background 0.2s, transform 0.2s;',
' padding: 0; overflow: hidden;',
'}',
'#tw-ai-chat-btn:hover { background: ' + BRAND_GREEN_DARK + '; transform: scale(1.05); }',
'#tw-ai-chat-btn svg { width: 28px; height: 28px; fill: #fff; }',
'#tw-ai-chat-btn img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 140%; height: 140%; object-fit: cover; object-position: center 25%; border-radius: 0; }',
'#tw-ai-chat-panel {',
' position: fixed; bottom: 96px; right: 24px; z-index: 99999;',
' width: 340px; max-width: calc(100vw - 32px);',
' height: 480px; max-height: calc(100vh - 120px);',
' background: #fff; border-radius: 16px;',
' box-shadow: 0 8px 40px rgba(0,0,0,0.18);',
' display: none; flex-direction: column; overflow: hidden;',
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
'}',
'#tw-ai-chat-panel.tw-open { display: flex; }',
'#tw-chat-header {',
' background: ' + BRAND_GREEN + '; color: #fff;',
' padding: 14px 16px; display: flex; align-items: center; gap: 10px;',
' flex-shrink: 0;',
'}',
'#tw-chat-header .tw-avatar {',
' width: 40px; height: 40px; border-radius: 50%;',
' background: rgba(255,255,255,0.15);',
' display: flex; align-items: center; justify-content: center; flex-shrink: 0;',
' overflow: hidden; position: relative;',
'}',
'#tw-chat-header .tw-avatar img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 140%; height: 140%; object-fit: cover; object-position: center 25%; border-radius: 0; }',
'#tw-chat-header .tw-avatar svg { width: 22px; height: 22px; fill: #fff; }',
'#tw-chat-header .tw-title { flex: 1; }',
'#tw-chat-header .tw-title strong { display: block; font-size: 14px; }',
'#tw-chat-header .tw-title span { font-size: 11px; opacity: 0.85; }',
'#tw-chat-close {',
' background: none; border: none; cursor: pointer;',
' color: #fff; font-size: 20px; line-height: 1; opacity: 0.8; padding: 4px;',
'}',
'#tw-chat-close:hover { opacity: 1; }',
'#tw-chat-messages {',
' flex: 1; overflow-y: auto; padding: 16px; display: flex;',
' flex-direction: column; gap: 10px; background: ' + BRAND_CREAM + ';',
'}',
'.tw-msg { max-width: 85%; line-height: 1.45; font-size: 13.5px; }',
'.tw-msg-bot {',
' align-self: flex-start; background: #fff; color: #222;',
' padding: 10px 13px; border-radius: 4px 14px 14px 14px;',
' box-shadow: 0 1px 4px rgba(0,0,0,0.08);',
'}',
'.tw-msg-user {',
' align-self: flex-end; background: ' + BRAND_GREEN + '; color: #fff;',
' padding: 10px 13px; border-radius: 14px 14px 4px 14px;',
'}',
'.tw-msg-typing {',
' align-self: flex-start; background: #fff; color: #888;',
' padding: 10px 16px; border-radius: 4px 14px 14px 14px;',
' font-style: italic; font-size: 13px;',
' box-shadow: 0 1px 4px rgba(0,0,0,0.08);',
'}',
'#tw-chat-input-row {',
' padding: 12px; border-top: 1px solid #e8e8e8;',
' display: flex; gap: 8px; background: #fff; flex-shrink: 0;',
'}',
'#tw-chat-input {',
' flex: 1; border: 1px solid #ddd; border-radius: 24px;',
' padding: 9px 14px; font-size: 13.5px; outline: none;',
' font-family: inherit; resize: none; line-height: 1.4;',
'}',
'#tw-chat-input:focus { border-color: ' + BRAND_GREEN + '; }',
'#tw-chat-send {',
' width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0;',
' background: ' + BRAND_GREEN + '; border: none; cursor: pointer;',
' display: flex; align-items: center; justify-content: center;',
' transition: background 0.2s;',
'}',
'#tw-chat-send:hover { background: ' + BRAND_GREEN_DARK + '; }',
'#tw-chat-send svg { width: 16px; height: 16px; fill: #fff; }',
'#tw-chat-send:disabled { opacity: 0.5; cursor: default; }',
'#tw-chat-bubble {',
' position: fixed; bottom: 96px; right: 24px; z-index: 99997;',
' background: #fff; color: #222; font-size: 13px; font-weight: 500;',
' padding: 9px 14px; border-radius: 20px;',
' box-shadow: 0 4px 16px rgba(0,0,0,0.15);',
' white-space: nowrap; cursor: pointer;',
' opacity: 0; transform: translateY(6px);',
' transition: opacity 0.3s ease, transform 0.3s ease;',
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
' border: 1px solid rgba(21,126,74,0.15);',
'}',
'#tw-chat-bubble.tw-bubble-visible { opacity: 1; transform: translateY(0); }',
'#tw-chat-bubble::after {',
' content: ""; position: absolute; bottom: -7px; right: 20px;',
' width: 0; height: 0;',
' border-left: 7px solid transparent;',
' border-right: 7px solid transparent;',
' border-top: 7px solid #fff;',
' filter: drop-shadow(0 2px 2px rgba(0,0,0,0.08));',
'}',
].join('\n');
document.head.appendChild(style);
// โโโ Build avatar HTML โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var pawIcon = '';
var sendIcon = '';
var avatarHTML = AVATAR_URL
? ''
: pawIcon;
// Floating button
var btn = document.createElement('button');
btn.id = WIDGET_ID + '-btn';
btn.setAttribute('aria-label', 'Chat with Timberwolf Help');
btn.innerHTML = AVATAR_URL ? '
' : pawIcon;
// Chat panel
var panel = document.createElement('div');
panel.id = WIDGET_ID + '-panel';
panel.innerHTML = [
'
', '
', '
',
].join('');
// Prompt bubble
var bubble = document.createElement('div');
bubble.id = WIDGET_ID + '-bubble';
bubble.textContent = 'Need help choosing?';
document.body.appendChild(btn);
document.body.appendChild(panel);
document.body.appendChild(bubble);
// โโโ DOM refs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
var messages = document.getElementById('tw-chat-messages');
var input = document.getElementById('tw-chat-input');
var sendBtn = document.getElementById('tw-chat-send');
var closeBtn = document.getElementById('tw-chat-close');
var isOpen = false;
var isWaiting = false;
var greeted = false;
var sessionRestored = false;
// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function renderMarkdown(text) {
return text
.replace(/&/g, '&').replace(//g, '>')
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/!\[([^\]]*)\]\((https?:\/\/[^\)]+)\)/g, '')
.replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, '$1');
}
function addMessage(text, type, skipSave) {
var div = document.createElement('div');
div.className = 'tw-msg tw-msg-' + type;
if (type === 'bot') {
div.innerHTML = renderMarkdown(text);
} else {
div.textContent = text;
}
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
if (!skipSave && (type === 'user' || type === 'bot')) {
session.history.push({ role: type === 'user' ? 'user' : 'assistant', content: text });
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
}
return div;
}
function restoreSession() {
if (sessionRestored || session.history.length === 0) return;
sessionRestored = true;
session.history.forEach(function (msg) {
addMessage(msg.content, msg.role === 'user' ? 'user' : 'bot', true);
});
}
function showTyping() {
var div = document.createElement('div');
div.className = 'tw-msg-typing';
div.id = 'tw-typing';
div.textContent = 'Timberwolf Help is typingโฆ';
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
return div;
}
function removeTyping() {
var el = document.getElementById('tw-typing');
if (el) el.remove();
}
function sendMessage() {
var text = input.value.trim();
if (!text || isWaiting) return;
addMessage(text, 'user');
input.value = '';
isWaiting = true;
sendBtn.disabled = true;
showTyping();
fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: text,
conversationId: conversationId,
history: session.history.slice(-20)
})
})
.then(function (res) { return res.json(); })
.then(function (data) {
removeTyping();
var reply = (data && data.reply) ? data.reply : 'Having trouble right now โ please try again shortly!';
addMessage(reply, 'bot');
})
.catch(function () {
removeTyping();
addMessage('Having trouble connecting. Please try again or contact our team directly!', 'bot');
})
.finally(function () {
isWaiting = false;
sendBtn.disabled = false;
input.focus();
});
}
function hideBubble() {
bubble.classList.remove('tw-bubble-visible');
}
function openChat() {
hideBubble();
panel.classList.add('tw-open');
isOpen = true;
input.focus();
if (session.history.length > 0 && !sessionRestored) {
restoreSession();
greeted = true;
}
if (!greeted) {
greeted = true;
setTimeout(function () {
isWaiting = true;
showTyping();
fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Hello',
conversationId: conversationId,
history: []
})
})
.then(function (res) { return res.json(); })
.then(function (data) {
removeTyping();
var reply = (data && data.reply) ? data.reply : 'Hi! How can I help you find the right formula today?';
addMessage(reply, 'bot');
})
.catch(function () {
removeTyping();
addMessage('Hi! How can I help you find the right formula today?', 'bot');
})
.finally(function () {
isWaiting = false;
sendBtn.disabled = false;
});
}, 300);
}
}
function closeChat() {
panel.classList.remove('tw-open');
isOpen = false;
}
// โโโ Events โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
btn.addEventListener('click', function () {
isOpen ? closeChat() : openChat();
});
closeBtn.addEventListener('click', closeChat);
sendBtn.addEventListener('click', sendMessage);
bubble.addEventListener('click', openChat);
input.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Show bubble after 3s if chat hasn't been opened yet
setTimeout(function () {
if (!isOpen && !greeted) {
bubble.classList.add('tw-bubble-visible');
}
}, 3000);
})();