'use strict'; const $ = s => document.querySelector(s); function wsURL(path){ return (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + path; } function fmtTime(ts){ const d = new Date(ts); return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); } // Render a chat message. `mine` = which side this page is ('host' | 'guest'). Text only - never innerHTML. function renderMsg(log, m, mine){ const el = document.createElement('div'); el.className = 'msg ' + (m.who === 'system' ? 'system' : (m.who === mine ? 'me' : 'them')); if (m.who !== 'system') { const w = document.createElement('span'); w.className = 'who'; w.textContent = (m.who === mine ? 'You' : m.from) + ' ยท ' + fmtTime(m.ts); el.appendChild(w); } el.appendChild(document.createTextNode(m.text)); const atBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 40; log.appendChild(el); if (atBottom || m.who === mine) log.scrollTop = log.scrollHeight; } function setMeter(el, peak){ // peak 0..1 linear -> bar on a -60..0 dBFS scale const db = peak > 0 ? 20*Math.log10(peak) : -100; const pct = Math.max(0, Math.min(100, (db + 60) / 60 * 100)); el.style.width = pct + '%'; el.className = db > -1 ? 'clip' : (db > -9 ? 'hot' : ''); return db; } function chatForm(form, ta, send){ form.addEventListener('submit', e => { e.preventDefault(); const t = ta.value.trim(); if (t) { send(t); ta.value=''; } ta.focus(); }); ta.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); form.requestSubmit(); } }); } function store(k, v){ try { if (v === undefined) return localStorage.getItem(k); localStorage.setItem(k, v); } catch(e) { return null; } }