(() => {
"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();
}
})();