(() => { "use strict"; const scriptElement = document.currentScript; if (!scriptElement) return; const UI = {"panelAriaLabel":"Czat wsparcia","closeAriaLabel":"Zamknąć","openAriaLabel":"Otwórz czat pomocy technicznej","title":"Pomoc prawna w Internecie","subtitle":"Poufnie • Szybko • Wybierzemy wyspecjalizowanego prawnika","footerHint":"Opisz sytuację w 1-2 zdaniach - otworzymy formularz kontaktowy. Odpowiedź zwykle przychodzi w ciągu ~15 minut.","enterText":"Wpisz swoją wiadomość...","ariaLabel":"Tekst pytania","next":"Kontynuować","buttonAskLine1":"Zadaj pytanie prawnikowi","buttonAskLine2":"Odpowiedź w ciągu ~15 minut","buttonOpen":"Czat jest otwarty","botHello":"Cześć! Jestem administratorem pomocy prawnej.","botAsk":"Proszę opisz swoją sytuację: co się stało i jaki efekt chcesz uzyskać?","botExplain":"Wiadomość będzie widoczna dla prawników zajmujących się tematem danej sprawy. Nie publikujemy danych osobowych – Ty decydujesz, co wskazać.","botIdle":"Jestem w kontakcie. Jeśli jest to dla Ciebie wygodne, podaj kilka szczegółów – w ten sposób szybko wybierzemy wyspecjalizowanego prawnika.","botThanks":"Dziękuję! Otwieram formularz. Po przesłaniu pytania otrzymasz odpowiedź od prawnika, zwykle w ciągu ~15 minut.","supportName":"Wsparcie"}; const referralCode = (scriptElement.getAttribute("data-referral") || window.location.host).trim(); const targetUrl = (scriptElement.getAttribute("data-target-url") || "https://prawnik.cc/new-question").trim(); const autoStart = (scriptElement.getAttribute("data-autostart") || "0").trim() === "1"; const supportAvatarUrl = (scriptElement.getAttribute("data-support-avatar") || "https://prawnik.cc/img/support-avatar.png").trim(); const existingWidget = document.getElementById("uristy-chat-widget-root"); if (existingWidget) return; const rootContainer = document.createElement("div"); rootContainer.id = "uristy-chat-widget-root"; document.documentElement.appendChild(rootContainer); const shadowRoot = rootContainer.attachShadow({ mode: "open" }); const styleElement = document.createElement("style"); styleElement.textContent = ` :host { all: initial; } .wrap { position: fixed; right: 18px; bottom: 18px; z-index: 2147483647; font-family: Arial, Helvetica, sans-serif; color: #111; display: flex; flex-direction: column; align-items: flex-end; gap: 12px; } /* Side button on the right side */ .sideButton { position: relative; border: 0; cursor: pointer; background: #7db960; color: #fff; border-radius: 14px; padding: 12px 14px; box-shadow: 0 14px 40px rgba(0,0,0,0.22); user-select: none; display: grid; grid-template-columns: 28px 1fr; gap: 10px; align-items: center; min-width: 220px; max-width: 260px; transition: transform 220ms ease, box-shadow 220ms ease, filter 220ms ease; will-change: transform; } .sideButton:hover { transform: translateY(-2px); box-shadow: 0 18px 48px rgba(0,0,0,0.24); } .sideButton:active { transform: translateY(0px); } .badgeAvatar { width: 28px; height: 28px; border-radius: 999px; object-fit: cover; background: rgba(255,255,255,0.22); border: 2px solid rgba(255,255,255,0.75); box-shadow: 0 0 0 0 rgba(255,255,255,0.0); animation: avatarPulse 1.8s ease-in-out infinite; } @keyframes avatarPulse { 0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255,255,255,0.0); } 50% { transform: scale(1.03); box-shadow: 0 0 0 7px rgba(255,255,255,0.20); } } .buttonText { display: grid; gap: 2px; line-height: 1.15; } .buttonText .l1 { font-weight: 900; font-size: 14px; white-space: nowrap; } .buttonText .l2 { font-weight: 700; font-size: 12px; color: rgba(255,255,255,0.82); white-space: nowrap; } /* Optional CTA pulse */ .pulse { animation: ctaPulse 1.55s ease-in-out infinite; filter: drop-shadow(0 10px 18px rgba(125,185,96,0.28)); } @keyframes ctaPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.02); } } /* Panel: smoother open/close */ .panel { width: min(380px, calc(100vw - 36px)); height: min(540px, calc(100vh - 120px)); background: #fff; border-radius: 16px; box-shadow: 0 22px 70px rgba(0,0,0,0.24); overflow: hidden; visibility: hidden; pointer-events: none; opacity: 0; transform: translateY(18px) scale(0.96); transform-origin: bottom right; transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1), opacity 280ms ease, visibility 0ms linear 360ms; will-change: transform, opacity; } .panel.open { visibility: visible; pointer-events: auto; opacity: 1; transform: translateY(0) scale(1); transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1), opacity 300ms ease, visibility 0ms; } .panelLayout { height: 100%; display: flex; flex-direction: column; min-height: 0; } .header { background: url("https://prawnik.cc/img/first-block-bg.jpg") center/cover no-repeat; padding: 14px 14px 12px 14px; color: #fff; position: relative; flex: 0 0 auto; } .header::after { content: ""; position: absolute; inset: 0; background: rgba(0,0,0,0.40); pointer-events: none; } .headerInner { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; } .title { font-size: 15px; font-weight: 900; line-height: 1.25; } .subtitle { font-size: 12px; opacity: 0.95; margin-top: 2px; line-height: 1.35; } .close { border: 0; background: rgba(255,255,255,0.18); color: #fff; border-radius: 10px; cursor: pointer; padding: 6px 10px; font-weight: 900; transition: background 140ms ease; } .close:hover { background: rgba(255,255,255,0.30); } .close:active { transform: translateY(1px); } .messages { padding: 14px; overflow: auto; background: #fafafa; flex: 1 1 auto; min-height: 0; overscroll-behavior: contain; } .row { display: flex; margin: 10px 0; } .row.left { justify-content: flex-start; } .row.right { justify-content: flex-end; } .leftWrap { display: grid; grid-template-columns: 1fr; gap: 0; align-items: end; max-width: 92%; } .bubble { max-width: 100%; padding: 10px 12px; border-radius: 14px; font-size: 13px; line-height: 1.38; white-space: pre-wrap; word-wrap: break-word; } .left .bubble { background: #fff; border: 1px solid #eee; box-shadow: 0 1px 0 rgba(0,0,0,0.04); } .right .bubble { background: #f0ad4e; color: #fff; max-width: 82%; } .typingBubble { display: inline-flex; align-items: center; gap: 6px; padding: 12px 12px; } .typingDots { display: inline-flex; align-items: center; gap: 6px; } .typingDots .dot { width: 6px; height: 6px; border-radius: 999px; background: #888; opacity: 0.45; animation: typing 1.2s infinite ease-in-out; } .typingDots .dot:nth-child(2) { animation-delay: 0.12s; } .typingDots .dot:nth-child(3) { animation-delay: 0.24s; } @keyframes typing { 0%, 100% { transform: translateY(0); opacity: 0.35; } 50% { transform: translateY(-3px); opacity: 0.85; } } .footer { padding: 12px; border-top: 1px solid #eee; background: #fff; display: grid; gap: 8px; flex: 0 0 auto; } .hint { font-size: 12px; color: #555; line-height: 1.35; } .inputRow { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: stretch; } textarea { resize: none; width: 100%; height: 64px; border-radius: 12px; border: 1px solid #ddd; padding: 10px 10px; font-size: 13px; outline: none; box-sizing: border-box; transition: border-color 140ms ease, box-shadow 140ms ease, height 180ms ease; } textarea:focus { height: 92px; border-color: #f0ad4e; box-shadow: 0 0 0 3px rgba(240,173,78,0.18); } .send { border: 0; border-radius: 12px; padding: 0; cursor: pointer; background: #111; color: #fff; font-weight: 900; height: auto; width: 44px; min-width: 44px; display: grid; place-items: center; align-self: stretch; transition: transform 140ms ease, opacity 140ms ease; } .send:active { transform: translateY(1px); } .send:disabled { opacity: 0.55; cursor: not-allowed; } .sendIcon { font-size: 18px; line-height: 1; } @supports (padding: env(safe-area-inset-right)) { .wrap { right: calc(18px + env(safe-area-inset-right)); bottom: calc(18px + env(safe-area-inset-bottom)); } } `; shadowRoot.appendChild(styleElement); const wrap = document.createElement("div"); wrap.className = "wrap"; wrap.innerHTML = ` `; shadowRoot.appendChild(wrap); const panel = shadowRoot.getElementById("panel"); const messages = shadowRoot.getElementById("messages"); const toggleBtn = shadowRoot.getElementById("toggleBtn"); const closeBtn = shadowRoot.getElementById("closeBtn"); const input = shadowRoot.getElementById("input"); const sendBtn = shadowRoot.getElementById("sendBtn"); if (!panel || !messages || !toggleBtn || !closeBtn || !input || !sendBtn) return; const state = { isOpen: false, hasBootMessages: false, pendingTimers: [], isTyping: false, idleTimerId: null, lastActivityAt: 0, idleMessageSent: false, conversation: [], }; const storageKey = "uristy_chat_widget_v1:" + referralCode + ":" + window.location.host + ":" + window.location.pathname; const safeSessionStorage = (() => { try { const storage = window.sessionStorage; const testKey = "__uristy_chat_test"; storage.setItem(testKey, "1"); storage.removeItem(testKey); return storage; } catch (e) { return null; } })(); const persistState = () => { if (!safeSessionStorage) return; try { const payload = { v: 1, isOpen: state.isOpen, hasBootMessages: state.hasBootMessages, idleMessageSent: state.idleMessageSent, lastActivityAt: state.lastActivityAt, messages: state.conversation.slice(-60), }; safeSessionStorage.setItem(storageKey, JSON.stringify(payload)); } catch (e) { // ignore } }; const restoreState = () => { if (!safeSessionStorage) return { shouldOpen: false }; try { const raw = safeSessionStorage.getItem(storageKey); if (!raw) return { shouldOpen: false }; const data = JSON.parse(raw); if (!data || typeof data !== "object") return { shouldOpen: false }; state.conversation = Array.isArray(data.messages) ? data.messages.filter((m) => m && typeof m === "object") : []; const hasHistory = state.conversation.some((m) => typeof m.text === "string" && m.text.trim().length > 0); state.hasBootMessages = Boolean(data.hasBootMessages) && hasHistory; state.idleMessageSent = Boolean(data.idleMessageSent) && hasHistory; state.lastActivityAt = Number(data.lastActivityAt) || 0; const shouldOpen = Boolean(data.isOpen); return { shouldOpen }; } catch (e) { return { shouldOpen: false }; } }; const rememberMessage = (side, text) => { const safeText = String(text ?? ""); if (!safeText) return; state.conversation.push({ side, text: safeText }); if (state.conversation.length > 80) state.conversation.splice(0, state.conversation.length - 80); persistState(); }; const clearIdleTimer = () => { if (!state.idleTimerId) return; window.clearTimeout(state.idleTimerId); state.idleTimerId = null; }; const scheduleIdleNudge = () => { if (!state.isOpen) return; if (state.idleMessageSent) return; clearIdleTimer(); const last = state.lastActivityAt || Date.now(); const elapsed = Date.now() - last; const delayMs = Math.max(0, 60_000 - elapsed); state.idleTimerId = window.setTimeout(() => { if (!state.isOpen) return; if (state.idleMessageSent) return; const idleForMs = Date.now() - (state.lastActivityAt || Date.now()); if (idleForMs < 60_000) { scheduleIdleNudge(); return; } (async () => { await simulateTypedMessage(UI.botIdle); state.idleMessageSent = true; persistState(); })(); }, delayMs); }; const touchActivity = () => { state.lastActivityAt = Date.now(); persistState(); scheduleIdleNudge(); }; const clearPendingTimers = () => { for (const timerId of state.pendingTimers) window.clearTimeout(timerId); state.pendingTimers = []; }; const scheduleTimeout = (callback, delayMs) => { const timerId = window.setTimeout(callback, delayMs); state.pendingTimers.push(timerId); return timerId; }; const sleep = (ms) => new Promise((resolve) => scheduleTimeout(resolve, ms)); const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const scrollToBottom = () => { messages.scrollTop = messages.scrollHeight; }; const appendLeftMessageWithAvatar = (text, options = {}) => { const persist = options.persist !== false; const row = document.createElement("div"); row.className = "row left"; const wrapElement = document.createElement("div"); wrapElement.className = "leftWrap"; const bubble = document.createElement("div"); bubble.className = "bubble"; bubble.textContent = text; wrapElement.appendChild(bubble); row.appendChild(wrapElement); messages.appendChild(row); scrollToBottom(); if (persist) rememberMessage("left", text); return bubble; }; const appendRightMessage = (text, options = {}) => { const persist = options.persist !== false; const row = document.createElement("div"); row.className = "row right"; const bubble = document.createElement("div"); bubble.className = "bubble"; bubble.textContent = text; row.appendChild(bubble); messages.appendChild(row); scrollToBottom(); if (persist) rememberMessage("right", text); }; const createTypingRow = () => { const row = document.createElement("div"); row.className = "row left"; row.setAttribute("data-typing", "1"); const wrapElement = document.createElement("div"); wrapElement.className = "leftWrap"; const bubble = document.createElement("div"); bubble.className = "bubble typingBubble"; bubble.innerHTML = ` `; wrapElement.appendChild(bubble); row.appendChild(wrapElement); messages.appendChild(row); scrollToBottom(); return row; }; const removeTypingRow = () => { const typingRow = messages.querySelector('[data-typing="1"]'); if (typingRow) typingRow.remove(); }; const simulateTypedMessage = async (text) => { if (state.isTyping) return; state.isTyping = true; removeTypingRow(); createTypingRow(); await sleep(randInt(520, 980)); removeTypingRow(); const bubble = appendLeftMessageWithAvatar("", { persist: false }); let currentText = ""; for (const char of text) { if (!state.isOpen) break; currentText += char; bubble.textContent = currentText; scrollToBottom(); const delayMs = char === "." || char === "!" || char === "?" ? randInt(220, 420) : char === "," || char === ":" || char === ";" ? randInt(120, 240) : randInt(18, 42); await sleep(delayMs); } rememberMessage("left", currentText); state.isTyping = false; await sleep(randInt(220, 520)); }; const openPanel = () => { if (state.isOpen) return; state.isOpen = true; panel.classList.add("open"); toggleBtn.classList.remove("pulse"); if (!state.hasBootMessages) bootMessages(); touchActivity(); persistState(); scheduleTimeout(() => input.focus(), 160); }; const closePanel = () => { if (!state.isOpen) return; state.isOpen = false; clearPendingTimers(); clearIdleTimer(); removeTypingRow(); state.isTyping = false; persistState(); panel.classList.remove("open"); }; const togglePanel = () => { if (state.isOpen) closePanel(); else openPanel(); }; const bootMessages = () => { state.hasBootMessages = true; persistState(); (async () => { await sleep(randInt(420, 780)); if (!state.isOpen && !autoStart) return; await simulateTypedMessage(UI.botHello); await simulateTypedMessage(UI.botAsk); await simulateTypedMessage(UI.botExplain); })(); }; const getTrimmedInput = () => String(input.value || "").trim(); const updateSendState = () => { sendBtn.disabled = getTrimmedInput().length < 10; }; const redirectToQuestionForm = (description) => { const url = new URL(targetUrl); url.searchParams.set("referral", referralCode); url.searchParams.set("description", description); window.location.assign(url.toString()); }; const handleSend = () => { const description = getTrimmedInput(); if (description.length < 10) return; appendRightMessage(description); touchActivity(); (async () => { removeTypingRow(); createTypingRow(); await sleep(randInt(420, 760)); removeTypingRow(); appendLeftMessageWithAvatar(UI.botThanks); await sleep(3500); redirectToQuestionForm(description); })(); }; // Close should always work toggleBtn.addEventListener("click", (event) => { event.preventDefault(); togglePanel(); }); closeBtn.addEventListener("click", (event) => { event.preventDefault(); closePanel(); }); input.addEventListener("input", () => { updateSendState(); touchActivity(); }); input.addEventListener("focus", touchActivity); input.addEventListener("click", touchActivity); input.addEventListener("keydown", (event) => { touchActivity(); if (event.key !== "Enter") return; if (event.shiftKey) return; event.preventDefault(); handleSend(); }); sendBtn.addEventListener("click", (event) => { event.preventDefault(); handleSend(); }); // Restore conversation & state (avoid re-typing on refresh) const restored = restoreState(); if (state.conversation.length) { for (const msg of state.conversation) { const side = msg.side === "right" ? "right" : "left"; const text = String(msg.text ?? ""); if (!text) continue; if (side === "right") appendRightMessage(text, { persist: false }); else appendLeftMessageWithAvatar(text, { persist: false }); } } if (restored.shouldOpen) openPanel(); else persistState(); if (autoStart) { toggleBtn.classList.add("pulse"); // keep closed by default; uncomment if needed: // openPanel(); } })();