diff --git a/static/script.js b/static/script.js index 197cb918..499c3855 100644 --- a/static/script.js +++ b/static/script.js @@ -338,8 +338,12 @@ function initWebSocket() { // Display or create a chat window for a character function showChatWindow(name) { if (chatWindows[name]) { - // Restore flex layout when reopening - chatWindows[name].style.display = 'flex'; + // Restore flex layout when reopening & bring to front + const existing = chatWindows[name]; + existing.style.display = 'flex'; + if (!window.__chatZ) window.__chatZ = 10000; + window.__chatZ += 1; + existing.style.zIndex = window.__chatZ; return; } const win = document.createElement('div'); @@ -380,6 +384,77 @@ function showChatWindow(name) { win.appendChild(form); document.body.appendChild(win); chatWindows[name] = win; + + /* --------------------------------------------------------- */ + /* enable dragging of the chat window via its header element */ + /* --------------------------------------------------------- */ + + // keep a static counter so newer windows can be brought to front + if (!window.__chatZ) window.__chatZ = 10000; + + let drag = false; + let startX = 0, startY = 0; + let startLeft = 0, startTop = 0; + + header.style.cursor = 'move'; + + // bring to front when interacting + const bringToFront = () => { + window.__chatZ += 1; + win.style.zIndex = window.__chatZ; + }; + + header.addEventListener('mousedown', e => { + // don't initiate drag when pressing the close button (or other clickable controls) + if (e.target.closest('button')) return; + e.preventDefault(); + drag = true; + bringToFront(); + startX = e.clientX; + startY = e.clientY; + // current absolute position + startLeft = win.offsetLeft; + startTop = win.offsetTop; + document.body.classList.add('noselect'); + }); + + window.addEventListener('mousemove', e => { + if (!drag) return; + const dx = e.clientX - startX; + const dy = e.clientY - startY; + win.style.left = `${startLeft + dx}px`; + win.style.top = `${startTop + dy}px`; + }); + + window.addEventListener('mouseup', () => { + drag = false; + document.body.classList.remove('noselect'); + }); + + /* touch support */ + header.addEventListener('touchstart', e => { + if (e.touches.length !== 1 || e.target.closest('button')) return; + drag = true; + bringToFront(); + const t = e.touches[0]; + startX = t.clientX; + startY = t.clientY; + startLeft = win.offsetLeft; + startTop = win.offsetTop; + }); + + window.addEventListener('touchmove', e => { + if (!drag || e.touches.length !== 1) return; + const t = e.touches[0]; + const dx = t.clientX - startX; + const dy = t.clientY - startY; + win.style.left = `${startLeft + dx}px`; + win.style.top = `${startTop + dy}px`; + }); + + window.addEventListener('touchend', () => { + drag = false; + }); } // Append a chat message to the correct window diff --git a/static/style.css b/static/style.css index d4655eaf..ee615d48 100644 --- a/static/style.css +++ b/static/style.css @@ -253,6 +253,7 @@ body { background: var(--accent); padding: 4px; color: #111; + cursor: move; /* indicates the header is draggable */ } .chat-close-btn { @@ -293,6 +294,11 @@ body { color: #fff; outline: none; } + +/* Prevent text selection while dragging chat windows */ +body.noselect, body.noselect * { + user-select: none !important; +} .stat.onlinetime::before { content: "🕑 "} .stat.deaths::before { content: "💀 "}