327 lines
9.9 KiB
JavaScript
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();
|
|
});
|
|
})();
|