diff --git a/ai-chat.js b/ai-chat.js
new file mode 100644
index 0000000..a92e2d5
--- /dev/null
+++ b/ai-chat.js
@@ -0,0 +1,156 @@
+(() => {
+ const introMessage =
+ "Witaj, jestem twoim wirtualnym agentem Krzak.org, w czym mogę Ci pomóc?";
+ const baseReplies = [
+ "Niestety, nie umiem jeszcze tego zrobić",
+ "Nie jestem pewien o co Ci chodzi",
+ ];
+ const offerFrom = [
+ "pedałów",
+ "pajaca",
+ "noży",
+ "gwoździ",
+ "przypraw",
+ "słoika",
+ "słoików",
+ "tabletek",
+ "spreju",
+ "narzędzi",
+ "samolotu",
+ "osrtzałki",
+ "szafki",
+ "pojemnika",
+ "pojemników",
+ "ładowarki",
+ "ładowarek",
+ "wieszaków",
+ "wieszaka",
+ "półki",
+ "półek",
+ ];
+ const offerTo = [
+ "gwoździ",
+ "roweru",
+ "krojenia cebuli",
+ "pedałów",
+ "latania",
+ "łatania",
+ "podatków",
+ "sprzątania",
+ "wyrywania zębów",
+ "zmywarki",
+ "śmieci",
+ "krojenia chleba",
+ "krojenia ogórków",
+ "krojenia pomidorów",
+ "krojenia cebuli",
+ "krojenia ogórków",
+ "krojenia pomidorów",
+ "jedzenia",
+ "picia",
+ "kuchni",
+ "książek",
+ ];
+
+ const chatRoot = document.createElement("div");
+ chatRoot.className = "ai-chat";
+ chatRoot.innerHTML = `
+
+
+ `;
+ document.body.appendChild(chatRoot);
+
+ const fab = chatRoot.querySelector(".ai-chat__fab");
+ const panel = chatRoot.querySelector(".ai-chat__panel");
+ const closeBtn = chatRoot.querySelector(".ai-chat__close");
+ const messages = chatRoot.querySelector(".ai-chat__messages");
+ const form = chatRoot.querySelector(".ai-chat__form");
+ const input = chatRoot.querySelector(".ai-chat__input");
+ const backToTop = document.getElementById("back-to-top");
+
+ function addMessage(text, fromUser) {
+ const bubble = document.createElement("p");
+ bubble.className = fromUser
+ ? "ai-chat__bubble ai-chat__bubble--user"
+ : "ai-chat__bubble";
+ bubble.textContent = text;
+ messages.appendChild(bubble);
+ messages.scrollTop = messages.scrollHeight;
+ }
+
+ function openChat() {
+ panel.classList.add("is-open");
+ panel.setAttribute("aria-hidden", "false");
+ fab.setAttribute("aria-expanded", "true");
+ if (!messages.hasChildNodes()) {
+ addMessage(introMessage, false);
+ }
+ input.focus();
+ }
+
+ function closeChat() {
+ panel.classList.remove("is-open");
+ panel.setAttribute("aria-hidden", "true");
+ fab.setAttribute("aria-expanded", "false");
+ }
+
+ function randomItem(items) {
+ return items[Math.floor(Math.random() * items.length)];
+ }
+
+ function buildReply() {
+ return `${randomItem(baseReplies)}, ale mogę Ci zaoferować kupno ${randomItem(offerFrom)} do ${randomItem(offerTo)}.`;
+ }
+
+ function syncChatPosition() {
+ if (!backToTop) {
+ chatRoot.classList.remove("ai-chat--raised");
+ return;
+ }
+ chatRoot.classList.toggle(
+ "ai-chat--raised",
+ backToTop.classList.contains("visible"),
+ );
+ }
+
+ fab.addEventListener("click", () => {
+ if (panel.classList.contains("is-open")) {
+ closeChat();
+ return;
+ }
+ openChat();
+ });
+
+ closeBtn.addEventListener("click", closeChat);
+
+ form.addEventListener("submit", (event) => {
+ event.preventDefault();
+ const text = input.value.trim();
+ if (!text) {
+ return;
+ }
+ addMessage(text, true);
+ addMessage(buildReply(), false);
+ input.value = "";
+ });
+
+ if (backToTop) {
+ const observer = new MutationObserver(syncChatPosition);
+ observer.observe(backToTop, {
+ attributes: true,
+ attributeFilter: ["class"],
+ });
+ window.addEventListener("resize", syncChatPosition);
+ }
+ syncChatPosition();
+})();
diff --git a/applications.html b/applications.html
index 6e194d1..821b95a 100644
--- a/applications.html
+++ b/applications.html
@@ -197,5 +197,6 @@
+