(function () { const log = document.getElementById("log"); const form = document.getElementById("chat-form"); const input = document.getElementById("msg"); const status = document.getElementById("status"); const me = document.getElementById("me").textContent.trim(); let socket; let reconnectDelay = 500; function el(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function atBottom() { return log.scrollHeight - log.scrollTop - log.clientHeight < 40; } function append(node) { const stick = atBottom(); log.appendChild(node); if (stick) log.scrollTop = log.scrollHeight; } function fmtTime(ms) { if (!ms) return ""; const d = new Date(ms); return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } function renderChat(msg) { const line = el("div", "chat-line"); if (msg.nick === me) line.classList.add("mine"); line.appendChild(el("span", "chat-nick", msg.nick)); line.appendChild(el("span", "chat-body", " " + msg.body)); line.appendChild(el("span", "chat-time", " " + fmtTime(msg.at))); append(line); } function renderSystem(msg) { append(el("div", "chat-system", msg.body)); } function handle(msg) { switch (msg.type) { case "history": log.innerHTML = ""; (msg.history || []).forEach(renderChat); break; case "chat": renderChat(msg); break; case "system": renderSystem(msg); break; } } function connect() { const proto = location.protocol === "https:" ? "wss" : "ws"; socket = new WebSocket(`${proto}://${location.host}/ws`); socket.onopen = function () { status.textContent = "connected ✓"; status.className = "status ok"; reconnectDelay = 500; }; socket.onmessage = function (event) { try { handle(JSON.parse(event.data)); } catch (e) { console.error("bad message", e); } }; socket.onclose = function () { status.textContent = "reconnecting…"; status.className = "status"; setTimeout(connect, reconnectDelay); reconnectDelay = Math.min(reconnectDelay * 2, 8000); }; socket.onerror = function () { socket.close(); }; } form.addEventListener("submit", function (e) { e.preventDefault(); const body = input.value.trim(); if (!body || !socket || socket.readyState !== WebSocket.OPEN) return; socket.send(JSON.stringify({ type: "chat", body: body })); input.value = ""; }); connect(); })();