Eventbuzz_MainWebsite/js/chat-demo.js

327 lines
9.9 KiB
JavaScript

(() => {
const CONVERSATION = [
{
a: "Can I register at the venue, or do I need to have registered online?",
b: "Walk-in registration is open today! Just bring a valid photo ID and we'll get you set up in minutes.",
},
{
a: "What if I don't have a confirmation email?",
b: "No problem — we can look you up by name at the counter.",
},
{
a: "Where's the registration counter?",
b: "North Atrium, ground floor — straight ahead from the main entrance, past the coffee stand.",
},
{
a: "How far is that from the main entrance?",
b: "About a 2-minute walk — signs are posted the whole way.",
},
{
a: "Is there parking nearby?",
b: "Yes, Lot B is right outside — free for attendees.",
},
{
a: "Perfect, thank you!",
b: "Anytime! Enjoy the event 🎉",
},
];
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function initChatDemo() {
const widget = document.querySelector(".chat-widget");
if (!widget || !window.gsap) return;
const messages = widget.querySelector(".chat-messages");
const textarea = widget.querySelector(".chat-input-bar textarea");
const sendBtn = widget.querySelector(".send-btn");
const status = widget.querySelector(".chat-header .status");
if (!messages || !textarea || !sendBtn || !status) return;
// Normalize avatar defs ids
const defs = widget.querySelector(".avatar-defs");
let avatarId = "demo";
if (defs) {
const groups = defs.querySelectorAll("g[id]");
groups.forEach((g) => {
if (g.id.startsWith("avatar-a")) g.id = "avatar-a-demo";
if (g.id.startsWith("avatar-b")) g.id = "avatar-b-demo";
});
}
let stopped = false;
const timelines = [];
const track = (tl) => {
timelines.push(tl);
tl.eventCallback("onComplete", () => {
const i = timelines.indexOf(tl);
if (i >= 0) timelines.splice(i, 1);
});
return tl;
};
const scrollBottom = () => {
messages.scrollTop = messages.scrollHeight;
};
const avatarSvg = (who) =>
`<svg class="avatar" viewBox="0 0 40 40"><use href="#avatar-${who}-demo"></use></svg>`;
const resizeInput = () => {
textarea.style.height = "auto";
textarea.style.height = `${textarea.scrollHeight}px`;
};
const typeIntoInput = (text, speed = 45) =>
new Promise((resolve) => {
textarea.value = "";
resizeInput();
let i = 0;
const id = setInterval(() => {
if (stopped) {
clearInterval(id);
resolve();
return;
}
textarea.value += text[i];
resizeInput();
i += 1;
if (i >= text.length) {
clearInterval(id);
resolve();
}
}, speed);
});
const addMessage = (who, text) => {
const row = document.createElement("div");
row.className = `msg-row from-${who}`;
row.innerHTML = `${avatarSvg(who)}<div class="bubble">${text}</div>`;
messages.appendChild(row);
scrollBottom();
window.gsap.fromTo(
row,
{ opacity: 0, y: 12, scale: 0.96 },
{ opacity: 1, y: 0, scale: 1, duration: 0.35, ease: "back.out(1.6)" }
);
};
const showTyping = (who) => {
const row = document.createElement("div");
row.className = `msg-row from-${who} typing-row`;
row.innerHTML = `${avatarSvg(who)}<div class="bubble"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>`;
messages.appendChild(row);
scrollBottom();
window.gsap.fromTo(row, { opacity: 0 }, { opacity: 1, duration: 0.2 });
const dots = row.querySelectorAll(".dot");
const tl = track(window.gsap.timeline({ repeat: -1 }));
tl.to(dots, {
y: -5,
duration: 0.28,
stagger: 0.12,
ease: "sine.inOut",
yoyo: true,
repeat: 1,
}).to({}, { duration: 0.15 });
return {
remove: () => {
tl.kill();
const i = timelines.indexOf(tl);
if (i >= 0) timelines.splice(i, 1);
window.gsap.to(row, {
opacity: 0,
duration: 0.15,
onComplete: () => row.remove(),
});
},
};
};
const pressSend = () =>
track(
window.gsap
.timeline()
.to(sendBtn, { scale: 0.85, duration: 0.09, ease: "power1.out" })
.to(sendBtn, { scale: 1, duration: 0.18, ease: "back.out(3)" })
);
(async () => {
while (!stopped) {
messages.innerHTML = "";
textarea.value = "";
textarea.style.height = "auto";
status.textContent = "online";
for (const turn of CONVERSATION) {
if (stopped) return;
await typeIntoInput(turn.a);
if (stopped) return;
await sleep(400);
if (stopped) return;
pressSend();
await sleep(150);
if (stopped) return;
textarea.value = "";
textarea.style.height = "auto";
addMessage("a", turn.a);
await sleep(300);
if (stopped) return;
status.textContent = "typing…";
const typing = showTyping("b");
await sleep(1300 + 400 * Math.random());
if (stopped) {
typing.remove();
return;
}
typing.remove();
await sleep(200);
if (stopped) return;
status.textContent = "online";
addMessage("b", turn.b);
await sleep(900);
}
await sleep(1600);
}
})();
window.addEventListener(
"pagehide",
() => {
stopped = true;
timelines.forEach((tl) => tl.kill());
window.gsap.killTweensOf([sendBtn, messages]);
},
{ once: true }
);
}
function initFlowDiagram() {
const root = document.querySelector(".eb-flow-v3");
if (!root || !window.gsap) return;
const beamLength = 80;
const prepare = (selector) => {
const path = root.querySelector(selector);
if (!path) return null;
const totalLength = path.getTotalLength();
window.gsap.set(path, {
strokeDasharray: `${beamLength} ${totalLength + beamLength}`,
strokeDashoffset: beamLength,
opacity: 0,
});
return { path, totalLength, beamLength };
};
const paths = {
step1_1: prepare("#glow-input-to-ai"),
step1_2: prepare("#glow-ai-to-middle"),
revReg: prepare("#glow-register-to-middle"),
revAge: prepare("#glow-agenda-to-middle"),
revChk: prepare("#glow-check-in-to-middle"),
revEng: prepare("#glow-engagement-to-middle"),
middleToAi: prepare("#glow-middle-to-ai"),
aiToInput: prepare("#glow-ai-to-input"),
};
if (Object.values(paths).some((p) => !p)) return;
const animatePath = (item, duration = 1) =>
window.gsap
.timeline()
.set(item.path, { opacity: 1, strokeDashoffset: item.beamLength })
.to(item.path, {
strokeDashoffset: -item.totalLength,
duration,
ease: "power1.inOut",
})
.set(item.path, { opacity: 0 });
const glowBoxes = (selectors, hold = 0.4, cls = "box-glow") => {
const els = (Array.isArray(selectors) ? selectors : [selectors])
.map((s) => root.querySelector(s))
.filter(Boolean);
return window.gsap
.timeline()
.to(els, {
onStart: () => els.forEach((el) => el.classList.add(cls)),
duration: 0.1,
})
.to({}, { duration: hold })
.to(els, {
onComplete: () => els.forEach((el) => el.classList.remove(cls)),
duration: 0.4,
});
};
const phone = root.querySelector("#phone");
if (!phone) return;
const middleBoxes = [
"#middle-container",
"#intelligence-box",
"#context-box",
"#intent-box",
];
const tl = window.gsap.timeline({ repeat: -1, repeatDelay: 0.5 });
tl.to(phone, {
onStart: () => phone.classList.add("phone-glow-orange"),
duration: 0.1,
})
.to({}, { duration: 0.4 })
.to(phone, {
onStart: () => phone.classList.remove("phone-glow-orange"),
duration: 0.4,
})
.add(animatePath(paths.step1_1, 1.2), "-=0.2")
.add(glowBoxes("#eventbuzz-ai-box", 0.5), "-=0.2")
.add(animatePath(paths.step1_2, 1), "-=0.3")
.add(glowBoxes(middleBoxes, 0.5), "-=0.2")
.add(glowBoxes("#register-box", 0.3, "box-glow-green"))
.add(animatePath(paths.revReg, 0.9), "-=0.2")
.add(glowBoxes("#agenda-box", 0.3, "box-glow-green"))
.add(animatePath(paths.revAge, 0.9), "-=0.2")
.add(glowBoxes("#check-in-box", 0.3, "box-glow-green"))
.add(animatePath(paths.revChk, 0.9), "-=0.2")
.add(glowBoxes("#engagement-box", 0.3, "box-glow-green"))
.add(animatePath(paths.revEng, 0.9), "-=0.2")
.add(glowBoxes(middleBoxes, 0.5, "box-glow-green"), "-=0.2")
.add(animatePath(paths.middleToAi, 0.8))
.add(glowBoxes("#eventbuzz-ai-box", 0.5, "box-glow-green"), "-=0.2")
.add(animatePath(paths.aiToInput, 1))
.to(
phone,
{
onStart: () => phone.classList.add("phone-glow-green"),
duration: 0.1,
},
"-=0.1"
)
.to({}, { duration: 2.5 })
.to(phone, {
onStart: () => phone.classList.remove("phone-glow-green"),
duration: 0.4,
});
window.addEventListener(
"pagehide",
() => {
tl.kill();
window.gsap.killTweensOf(root.querySelectorAll("path, rect, g"));
phone.classList.remove("phone-glow-orange", "phone-glow-green");
root
.querySelectorAll(".box-glow, .box-glow-green")
.forEach((el) => el.classList.remove("box-glow", "box-glow-green"));
},
{ once: true }
);
}
document.addEventListener("DOMContentLoaded", () => {
initChatDemo();
initFlowDiagram();
});
})();