You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
5.2 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>SA-MP Chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0e1a; color: #e0e0e0; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
.header { background: #111827; padding: 12px 16px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #1f2937; flex-shrink: 0; }
.header a { color: #6b7280; text-decoration: none; font-size: 20px; }
.header h1 { font-size: 16px; font-weight: 600; color: #f3f4f6; }
.status { margin-left: auto; width: 8px; height: 8px; border-radius: 50%; background: #ef4444; transition: background 0.3s; }
.status.on { background: #22c55e; }
.messages { flex: 1; overflow-y: auto; padding: 8px 12px; display: flex; flex-direction: column; gap: 2px; }
.msg { padding: 4px 8px; border-radius: 4px; font-family: 'Consolas', 'SF Mono', monospace; font-size: 13px; line-height: 1.5; word-wrap: break-word; animation: fi 0.15s ease; }
.msg:hover { background: rgba(255,255,255,0.03); }
.msg .t { color: #4b5563; font-size: 11px; margin-right: 6px; user-select: none; }
.msg.me { border-left: 2px solid #3b82f6; background: rgba(59,130,246,0.06); }
@keyframes fi { from { opacity:0; transform:translateY(4px); } to { opacity:1; transform:translateY(0); } }
.input-area { background: #111827; padding: 10px 12px; border-top: 1px solid #1f2937; display: flex; gap: 8px; flex-shrink: 0; }
.input-area input { flex: 1; background: #1f2937; border: 1px solid #374151; border-radius: 8px; padding: 10px 14px; color: #f3f4f6; font-size: 14px; outline: none; }
.input-area input:focus { border-color: #3b82f6; }
.input-area input::placeholder { color: #6b7280; }
.input-area button { background: #3b82f6; color: white; border: none; border-radius: 8px; padding: 10px 20px; font-size: 14px; font-weight: 600; cursor: pointer; }
.input-area button:hover { background: #2563eb; }
.input-area button:active { background: #1d4ed8; }
.messages::-webkit-scrollbar { width: 6px; }
.messages::-webkit-scrollbar-track { background: transparent; }
.messages::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
</style>
</head>
<body>
<div class="header">
<a href="/">&#8592;</a>
<h1>SA-MP Chat</h1>
<div class="status" id="st"></div>
</div>
<div class="messages" id="msgs"></div>
<div class="input-area">
<input type="text" id="inp" placeholder="Type a message..." maxlength="144" autocomplete="off">
<button onclick="send()">Send</button>
</div>
<script>
const msgs = document.getElementById('msgs');
const inp = document.getElementById('inp');
const st = document.getElementById('st');
let ws, atBottom = true;
msgs.addEventListener('scroll', () => {
atBottom = msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 40;
});
function scroll() { if (atBottom) msgs.scrollTop = msgs.scrollHeight; }
function colorToCSS(c) {
let u = c >>> 0, r = (u>>24)&0xFF, g = (u>>16)&0xFF, b = (u>>8)&0xFF, a = u&0xFF;
if (!a) a = 255;
return `rgba(${r},${g},${b},${(a/255).toFixed(2)})`;
}
function parseColors(text) {
text = text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
let r = '', parts = text.split(/(\{[0-9A-Fa-f]{6}\})/), inSpan = false;
for (let p of parts) {
let m = p.match(/^\{([0-9A-Fa-f]{6})\}$/);
if (m) { if (inSpan) r += '</span>'; r += `<span style="color:#${m[1]}">`; inSpan = true; }
else r += p;
}
if (inSpan) r += '</span>';
return r;
}
function addMsg(ev, args) {
if (ev !== 'onServerMessage') return;
const [color, text] = args;
const d = document.createElement('div');
d.className = 'msg';
const now = new Date().toTimeString().slice(0,8);
const css = color ? ` style="color:${colorToCSS(color)}"` : '';
d.innerHTML = `<span class="t">${now}</span><span${css}>${parseColors(text)}</span>`;
msgs.appendChild(d);
while (msgs.children.length > 300) msgs.removeChild(msgs.firstChild);
scroll();
}
function addSent(text) {
const d = document.createElement('div');
d.className = 'msg me';
const now = new Date().toTimeString().slice(0,8);
d.innerHTML = `<span class="t">${now}</span><span style="color:#93c5fd">${text.replace(/</g,'&lt;')}</span>`;
msgs.appendChild(d);
scroll();
}
function send() {
const text = inp.value.trim();
if (!text) return;
fetch('/api/chat/send', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({text}) });
addSent(text);
inp.value = '';
inp.focus();
}
inp.addEventListener('keydown', e => { if (e.key === 'Enter') send(); });
function connect() {
ws = new WebSocket('ws://'+location.host+'/ws');
ws.onopen = () => st.className = 'status on';
ws.onclose = () => { st.className = 'status'; setTimeout(connect, 2000); };
ws.onerror = () => ws.close();
ws.onmessage = e => {
try {
const d = JSON.parse(e.data);
if (d.type === 'event') addMsg(d.event, d.args);
} catch(err) {}
};
}
connect();
inp.focus();
</script>
</body>
</html>