foxygit / infodisplay Log in
commits tags

/kiosk/templates/chat_mobile.html · 29.7 KB

raw
<!DOCTYPE html>
<html lang="sv">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>Livechat</title>
  <script src="/static/socket.io.min.js"></script>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }

    :root {
      --bg:       #0f0f0f;
      --surface:  #1a1a1a;
      --border:   #2a2a2a;
      --text:     #e8e8e8;
      --muted:    #777;
      --accent:   #2563eb;
      --accent-h: #1d4ed8;
    }

    html, body {
      height: 100%;
      background: var(--bg);
      color: var(--text);
      font-family: 'Segoe UI', system-ui, sans-serif;
      font-size: 15px;
    }

    /* ====== AUTH SCREEN ====== */
    #auth-screen {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      padding: 2rem 1.5rem;
      gap: 1rem;
    }

    #auth-screen h1 {
      font-size: 1.6rem;
      font-weight: 700;
      color: #fff;
      margin-bottom: .25rem;
    }

    /* Tab bar */
    .tab-bar {
      display: flex;
      width: 100%;
      max-width: 340px;
      background: var(--surface);
      border-radius: 10px;
      padding: .25rem;
      gap: .25rem;
    }

    .tab-btn {
      flex: 1;
      padding: .6rem;
      border: none;
      border-radius: 8px;
      background: transparent;
      color: var(--muted);
      font-size: .95rem;
      font-weight: 600;
      cursor: pointer;
      transition: background .15s, color .15s;
    }

    .tab-btn.active {
      background: var(--accent);
      color: #fff;
    }

    /* Forms */
    .auth-form {
      display: none;
      flex-direction: column;
      gap: .75rem;
      width: 100%;
      max-width: 340px;
    }

    .auth-form.visible { display: flex; }

    input[type=text], input[type=password] {
      width: 100%;
      background: var(--surface);
      border: 1px solid var(--border);
      color: var(--text);
      padding: .75rem 1rem;
      border-radius: 10px;
      font-size: 1rem;
    }

    input::placeholder { color: var(--muted); }
    input:focus { outline: 2px solid var(--accent); border-color: transparent; }

    .auth-btn {
      width: 100%;
      background: var(--accent);
      color: #fff;
      border: none;
      padding: .85rem 1rem;
      border-radius: 10px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      margin-top: .25rem;
    }

    .auth-btn:hover { background: var(--accent-h); }
    .auth-btn:disabled { opacity: .5; cursor: default; }

    .auth-error {
      color: #f87171;
      font-size: .85rem;
      min-height: 1.2em;
      text-align: center;
    }

    /* Avatar grid (register only) */
    .avatar-label {
      font-size: .8rem;
      color: var(--muted);
      font-weight: 600;
      letter-spacing: .04em;
      text-transform: uppercase;
    }

    .avatar-pick {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: .6rem;
      width: 100%;
    }

    .avatar-pick-circle {
      width: 80px; height: 80px;
      border-radius: 50%;
      background: var(--surface);
      border: 2px dashed var(--border);
      display: flex; align-items: center; justify-content: center;
      overflow: hidden;
      cursor: pointer;
      transition: border-color .15s;
    }

    .avatar-pick-circle:hover { border-color: var(--accent); }
    .avatar-pick-circle img { width: 100%; height: 100%; object-fit: cover; display: none; }
    .avatar-pick-circle .pick-icon { font-size: 2rem; color: var(--muted); }

    .avatar-pick-hint {
      font-size: .8rem;
      color: var(--muted);
      text-align: center;
    }

    /* ====== CHAT SCREEN ====== */
    #chat-screen {
      display: none;
      flex-direction: column;
      height: 100vh;
    }

    #chat-topbar {
      display: flex;
      align-items: center;
      gap: .6rem;
      padding: .75rem 1rem;
      background: var(--surface);
      border-bottom: 1px solid var(--border);
      flex-shrink: 0;
    }

    #chat-topbar .me-avatar { font-size: 1.3rem; }
    #chat-topbar .me-name   { font-weight: 600; font-size: .95rem; }

    #chat-topbar .viewers {
      margin-left: auto;
      font-size: .8rem;
      color: var(--muted);
      background: var(--bg);
      padding: .2rem .6rem;
      border-radius: 99px;
    }

    #logout-btn {
      background: none;
      border: none;
      color: var(--muted);
      font-size: 1.1rem;
      cursor: pointer;
      padding: .2rem .35rem;
      border-radius: 6px;
    }
    #logout-btn:hover { color: #f87171; }

    #avatar-upload-btn {
      position: relative;
      width: 2rem; height: 2rem;
      border-radius: 50%;
      overflow: hidden;
      flex-shrink: 0;
      cursor: pointer;
      border: 2px solid #333;
    }
    #avatar-upload-btn img {
      width: 100%; height: 100%;
      object-fit: cover;
    }
    #avatar-upload-btn .avatar-overlay {
      display: none;
      position: absolute;
      inset: 0;
      background: #0009;
      font-size: .65rem;
      color: #fff;
      align-items: center;
      justify-content: center;
    }
    #avatar-upload-btn:hover .avatar-overlay { display: flex; }
    #avatar-file { display: none; }

    #chat-log {
      flex: 1;
      overflow-y: auto;
      padding: .75rem 1rem;
      display: flex;
      flex-direction: column;
      gap: .55rem;
    }

    .cm { display: flex; gap: .5rem; align-items: flex-start; }
    .cm.mine { flex-direction: row-reverse; }

    .cm-avatar {
      width: 1.9rem; height: 1.9rem;
      border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-size: 1rem;
      flex-shrink: 0;
      overflow: hidden;
    }

    .cm-bubble {
      max-width: 78%;
      padding: .5rem .75rem;
      border-radius: 14px;
      font-size: .95rem;
      line-height: 1.4;
      word-break: break-word;
    }

    .cm:not(.mine) .cm-bubble {
      background: var(--surface);
      border-top-left-radius: 4px;
    }

    .cm.mine .cm-bubble {
      background: var(--accent);
      color: #fff;
      border-top-right-radius: 4px;
    }

    .cm-name {
      font-size: .72rem;
      font-weight: 700;
      margin-bottom: .15rem;
    }

    .cm.mine .cm-name { display: none; }

    .cm.system { justify-content: center; }
    .cm.system .cm-bubble {
      background: transparent;
      color: var(--muted);
      font-size: .8rem;
      font-style: italic;
      text-align: center;
      padding: .2rem;
    }

    #input-row {
      display: flex;
      gap: .5rem;
      padding: .65rem .75rem;
      background: var(--surface);
      border-top: 1px solid var(--border);
      flex-shrink: 0;
    }

    #msg-input {
      flex: 1;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: .6rem .85rem;
      border-radius: 20px;
      font-size: .95rem;
      resize: none;
      min-height: 2.4rem;
      max-height: 6rem;
      overflow-y: auto;
      line-height: 1.4;
      font-family: inherit;
    }

    #msg-input::placeholder { color: var(--muted); }
    #msg-input:focus { outline: 2px solid var(--accent); border-color: transparent; }

    #send-btn {
      width: 2.4rem; height: 2.4rem;
      border-radius: 50%;
      background: var(--accent);
      color: #fff;
      border: none;
      font-size: 1.1rem;
      cursor: pointer;
      flex-shrink: 0;
      align-self: flex-end;
      display: flex; align-items: center; justify-content: center;
    }

    #send-btn:disabled { background: #333; color: #555; cursor: default; }
    #send-btn:not(:disabled):hover { background: var(--accent-h); }

    /* Bifogningsknapp */
    #attach-btn {
      width: 2.4rem; height: 2.4rem;
      border-radius: 50%;
      background: var(--surface);
      color: var(--text);
      border: 1px solid var(--border);
      font-size: 1.25rem;
      cursor: pointer;
      flex-shrink: 0;
      align-self: flex-end;
      display: flex; align-items: center; justify-content: center;
      transition: background .15s;
    }
    #attach-btn:hover { background: #2a2a2a; }
    #attach-btn.open  { background: var(--accent); border-color: var(--accent); color:#fff; }

    /* Bifogningspanel */
    #attach-panel {
      display: none;
      padding: .5rem .75rem .4rem;
      background: var(--surface);
      border-top: 1px solid var(--border);
      gap: .5rem;
      flex-wrap: wrap;
    }
    .attach-opt {
      flex: 1;
      min-width: 80px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: .3rem;
      padding: .65rem .5rem;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 12px;
      cursor: pointer;
      font-size: .8rem;
      color: var(--muted);
      transition: border-color .15s, color .15s;
    }
    .attach-opt:hover { border-color: var(--accent); color: var(--text); }
    .attach-opt .opt-icon { font-size: 1.6rem; line-height: 1; }

    /* GIF-URL-rad */
    #gif-url-row {
      display: none;
      gap: .4rem;
      padding: .4rem .75rem .5rem;
      background: var(--surface);
      border-top: 1px solid var(--border);
    }
    #gif-url-input {
      flex: 1;
      background: var(--bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: .55rem .85rem;
      border-radius: 20px;
      font-size: .9rem;
    }
    #gif-url-input::placeholder { color: var(--muted); }
    #gif-url-send {
      background: var(--accent); color: #fff; border: none;
      padding: .55rem 1rem; border-radius: 20px; font-size: .9rem; cursor: pointer;
    }

    /* Upload-indikator */
    #upload-indicator {
      display: none;
      align-items: center;
      gap: .5rem;
      padding: .4rem .75rem;
      background: var(--surface);
      border-top: 1px solid var(--border);
      font-size: .82rem;
      color: var(--muted);
    }
    #upload-bar { flex:1; height:4px; background:#333; border-radius:2px; overflow:hidden; }
    #upload-fill { height:100%; background: var(--accent); width:0%; transition:width .1s; }

    /* Kommandotips */
    #cmd-hint {
      display: none;
      background: var(--surface);
      border-top: 1px solid var(--border);
      padding: .3rem .5rem;
      flex-shrink: 0;
    }
    .cmd-item {
      padding: .45rem .6rem;
      border-radius: 8px;
      font-size: .88rem;
      cursor: pointer;
      color: var(--text);
    }
    .cmd-item:hover { background: #ffffff0d; }
    .cmd-item strong { color: #818cf8; }

    /* Förslag-kort i chatten */
    .cm-suggestion {
      border-left: 3px solid var(--accent);
      padding: .4rem .65rem;
      background: #2563eb18;
      border-radius: 0 8px 8px 0;
      margin-top: .2rem;
      font-size: .85rem;
    }
    .cm-suggestion .sug-label { color: #818cf8; font-size: .75rem; margin-bottom: .2rem; }
    .cm-suggestion .sug-url   { color: #e8e8e8; word-break: break-all; }

    /* Inline-bilder */
    .cm-img { max-width: 220px; max-height: 180px; border-radius: 8px; display: block; margin-top: .25rem; }
  </style>
</head>
<body>

<!-- AUTH SCREEN -->
<div id="auth-screen">
  <h1>💬 Livechat</h1>

  <div class="tab-bar">
    <button class="tab-btn active" id="tab-login-btn"    onclick="showTab('login')">Logga in</button>
    <button class="tab-btn"        id="tab-register-btn" onclick="showTab('register')">Registrera</button>
  </div>

  <!-- Login form -->
  <form class="auth-form visible" id="login-form" onsubmit="return false">
    <input type="text"     id="login-username" placeholder="Användarnamn" autocomplete="username" autocorrect="off" autocapitalize="none">
    <input type="password" id="login-password" placeholder="Lösenord"     autocomplete="current-password">
    <div class="auth-error" id="login-error"></div>
    <button class="auth-btn" id="login-btn" onclick="doLogin()">Logga in</button>
  </form>

  <!-- Register form -->
  <form class="auth-form" id="register-form" onsubmit="return false">
    <input type="text"     id="reg-username" placeholder="Välj användarnamn" autocomplete="username"     autocorrect="off" autocapitalize="none" maxlength="32">
    <input type="password" id="reg-password" placeholder="Välj lösenord"     autocomplete="new-password">
    <div class="avatar-label">Profilbild (obligatorisk)</div>
    <div class="avatar-pick">
      <div class="avatar-pick-circle" id="reg-avatar-circle" onclick="document.getElementById('reg-avatar-file').click()">
        <img id="reg-avatar-preview" alt="">
        <span class="pick-icon" id="reg-avatar-icon">📷</span>
      </div>
      <div class="avatar-pick-hint">Tryck för att välja bild från kameran eller bildbiblioteket</div>
      <input type="file" id="reg-avatar-file" accept="image/*" capture="environment" style="display:none">
    </div>
    <div class="auth-error" id="reg-error"></div>
    <button class="auth-btn" id="reg-btn" onclick="doRegister()">Skapa konto</button>
  </form>
</div>

<!-- CHAT SCREEN -->
<div id="chat-screen">
  <div id="chat-topbar">
    <div id="avatar-upload-btn" title="Byt profilbild" onclick="document.getElementById('avatar-file').click()">
      <img id="me-avatar-img" src="" alt="" style="display:none">
      <span id="me-avatar" style="font-size:1.3rem;display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></span>
      <div class="avatar-overlay">📷</div>
    </div>
    <input type="file" id="avatar-file" accept="image/jpeg,image/png,image/webp">
    <span class="me-name"   id="me-name"></span>
    <span class="viewers"   id="viewer-count">0 aktiva</span>
    <button id="logout-btn" title="Logga ut" onclick="doLogout()">✕</button>
  </div>

  <div id="chat-log"></div>

  <!-- Upload-indikator -->
  <div id="upload-indicator">
    <span id="upload-label">Laddar upp...</span>
    <div id="upload-bar"><div id="upload-fill"></div></div>
  </div>

  <!-- GIF/bild URL-rad -->
  <div id="gif-url-row">
    <input id="gif-url-input" type="url" placeholder="Klistra in bild- eller GIF-URL...">
    <button id="gif-url-send">Skicka</button>
  </div>

  <!-- Bifogningspanel -->
  <div id="attach-panel" style="display:none">
    <div class="attach-opt" onclick="triggerFileInput('camera')">
      <span class="opt-icon">📷</span>
      <span>Kamera</span>
    </div>
    <div class="attach-opt" onclick="triggerFileInput('gallery')">
      <span class="opt-icon">🖼</span>
      <span>Galleri</span>
    </div>
    <div class="attach-opt" onclick="showGifUrlRow()">
      <span class="opt-icon">🔗</span>
      <span>Bild-URL</span>
    </div>
  </div>

  <!-- Dolda file inputs -->
  <input type="file" id="file-camera"  accept="image/*" capture="environment" style="display:none">
  <input type="file" id="file-gallery" accept="image/*,image/gif" style="display:none">

  <div id="cmd-hint">
    <div class="cmd-item" onclick="insertCmd('/spela ')">🎬 <strong>/spela</strong> URL — Föreslå vad som visas på displayen</div>
  </div>
  <div id="input-row">
    <button id="attach-btn" title="Bifoga bild">+</button>
    <textarea id="msg-input" placeholder="Skriv ett meddelande..." rows="1"></textarea>
    <button id="send-btn" disabled>➤</button>
  </div>
</div>

<script>
const AVATARS = ['😀','😎','🤖','🦊','🐼','🐸','🦁','🐯','🦄','🐻',
                 '🌟','🔥','⚡','🎮','🎵','🍕','🚀','💎','🌈','👾'];

const TOKEN_KEY = 'kiosk_chat_token';
const socket    = io('/chat');
let currentUser = null;  // {username, emoji, color, token}

// --- Innehållsrendering ---
const _IMG_RE = /^(?:https?:\/\/|\/)[^\s"<>]+\.(gif|jpe?g|png|webp|avif)(\?[^\s"<>]*)?$/i;

function _renderContent(text) {
  const t = text.trim();
  if (_IMG_RE.test(t)) {
    const safe = t.replace(/"/g, '%22');
    return `<img src="${safe}" class="cm-img" loading="lazy" onerror="this.style.display='none'">`;
  }
  return escHtml(text);
}

function _suggestionHtml(url) {
  const short = url.length > 55 ? url.slice(0, 52) + '…' : url;
  return `<div class="cm-suggestion">
    <div class="sug-label">🎬 Förslag till displayen</div>
    <div class="sug-url">${escHtml(short)}</div>
  </div>`;
}

function insertCmd(cmd) {
  msgInput.value = cmd;
  msgInput.focus();
  msgInput.dispatchEvent(new Event('input'));
  document.getElementById('cmd-hint').style.display = 'none';
}

// ---- Avatar pick (register) ----
let regAvatarFile = null;

document.getElementById('reg-avatar-file').addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (!file) return;
  regAvatarFile = file;
  const preview = document.getElementById('reg-avatar-preview');
  const icon    = document.getElementById('reg-avatar-icon');
  const url = URL.createObjectURL(file);
  preview.src = url;
  preview.style.display = 'block';
  icon.style.display    = 'none';
});

// ---- Tab switching ----
function showTab(tab) {
  document.getElementById('login-form').classList.toggle('visible',    tab === 'login');
  document.getElementById('register-form').classList.toggle('visible', tab === 'register');
  document.getElementById('tab-login-btn').classList.toggle('active',    tab === 'login');
  document.getElementById('tab-register-btn').classList.toggle('active', tab === 'register');
  document.getElementById('login-error').textContent = '';
  document.getElementById('reg-error').textContent   = '';
  // Reset avatar picker when switching tabs
  regAvatarFile = null;
  document.getElementById('reg-avatar-preview').style.display = 'none';
  document.getElementById('reg-avatar-preview').src = '';
  document.getElementById('reg-avatar-icon').style.display = '';
  document.getElementById('reg-avatar-file').value = '';
}

// ---- Auth helpers ----
function escHtml(s) {
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

async function doLogin() {
  const username = document.getElementById('login-username').value.trim();
  const password = document.getElementById('login-password').value;
  const errEl    = document.getElementById('login-error');
  if (!username || !password) { errEl.textContent = 'Fyll i alla fält'; return; }
  document.getElementById('login-btn').disabled = true;
  try {
    const r = await fetch('/api/chat/login', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({username, password}),
    });
    const d = await r.json();
    if (!r.ok) { errEl.textContent = d.error || 'Inloggning misslyckades'; return; }
    localStorage.setItem(TOKEN_KEY, d.token);
    enterChat(d);
  } catch {
    errEl.textContent = 'Nätverksfel, försök igen';
  } finally {
    document.getElementById('login-btn').disabled = false;
  }
}

async function doRegister() {
  const username = document.getElementById('reg-username').value.trim();
  const password = document.getElementById('reg-password').value;
  const errEl    = document.getElementById('reg-error');
  if (!username || !password) { errEl.textContent = 'Fyll i alla fält'; return; }
  if (!regAvatarFile) { errEl.textContent = 'Välj en profilbild för att fortsätta'; return; }
  document.getElementById('reg-btn').disabled = true;
  try {
    const randomEmoji = AVATARS[Math.floor(Math.random() * AVATARS.length)];
    const r = await fetch('/api/chat/register', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({username, password, emoji: randomEmoji}),
    });
    const d = await r.json();
    if (!r.ok) { errEl.textContent = d.error || 'Registrering misslyckades'; return; }
    // Upload avatar immediately after registration
    const fd = new FormData();
    fd.append('token', d.token);
    fd.append('avatar', regAvatarFile);
    const ar = await fetch('/api/chat/avatar', {method: 'POST', body: fd});
    if (ar.ok) {
      const ad = await ar.json();
      d.avatar_url = ad.avatar_url;
    }
    localStorage.setItem(TOKEN_KEY, d.token);
    enterChat(d);
  } catch {
    errEl.textContent = 'Nätverksfel, försök igen';
  } finally {
    document.getElementById('reg-btn').disabled = false;
  }
}

function setMyAvatar(avatarUrl, emoji) {
  const img  = document.getElementById('me-avatar-img');
  const span = document.getElementById('me-avatar');
  if (avatarUrl) {
    img.src           = avatarUrl + '?t=' + Date.now();
    img.style.display = 'block';
    span.style.display = 'none';
  } else {
    img.style.display  = 'none';
    span.style.display = 'flex';
    span.textContent   = emoji || '?';
  }
}

function enterChat(user) {
  currentUser = user;
  document.getElementById('auth-screen').style.display = 'none';
  const cs = document.getElementById('chat-screen');
  cs.style.display = 'flex';
  setMyAvatar(user.avatar_url, user.emoji);
  document.getElementById('me-name').textContent = user.username;
  document.getElementById('msg-input').focus();
  socket.emit('join', {token: user.token});
}

// ---- Avatar upload ----
document.getElementById('avatar-file').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file || !currentUser) return;
  const fd = new FormData();
  fd.append('token',  currentUser.token);
  fd.append('avatar', file);
  try {
    const r = await fetch('/api/chat/avatar', {method: 'POST', body: fd});
    if (!r.ok) return;
    const d = await r.json();
    currentUser.avatar_url = d.avatar_url;
    setMyAvatar(d.avatar_url, currentUser.emoji);
  } catch {}
  e.target.value = '';
});

function doLogout() {
  localStorage.removeItem(TOKEN_KEY);
  currentUser = null;
  socket.disconnect();
  document.getElementById('chat-screen').style.display = 'none';
  document.getElementById('auth-screen').style.display = 'flex';
  document.getElementById('chat-log').innerHTML = '';
  document.getElementById('login-password').value = '';
  socket.connect();
}

// ---- Auto-login from stored token ----
(async function tryAutoLogin() {
  const token = localStorage.getItem(TOKEN_KEY);
  if (!token) return;
  try {
    const r = await fetch('/api/chat/validate', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({token}),
    });
    if (!r.ok) { localStorage.removeItem(TOKEN_KEY); return; }
    const d = await r.json();
    enterChat({...d, token});
  } catch {}
})();

// ---- Socket events ----
socket.on('chat_history', (msgs) => {
  if (!currentUser) return;
  chatLog.innerHTML = '';
  msgs.forEach(m => {
    if (m.kind === 'play_suggestion') {
      if (m.name === currentUser.username) _addMySuggestion(m.text);
      else _addOtherSuggestion(m);
    } else if (m.name === currentUser.username) {
      addMyMsg(m.text);
    } else {
      addOtherMsg(m);
    }
  });
});

socket.on('chat_message', (d) => {
  if (!currentUser) return;
  if (d.name !== currentUser.username) addOtherMsg(d);
});

socket.on('play_suggestion', (d) => {
  if (!currentUser) return;
  if (d.name === currentUser.username) _addMySuggestion(d.text);
  else _addOtherSuggestion(d);
});

socket.on('system_message', (d) => {
  addSystemMsg(d.text);
});

socket.on('viewer_count', (d) => {
  document.getElementById('viewer-count').textContent = d.count + ' aktiva';
});

socket.on('auth_error', (d) => {
  localStorage.removeItem(TOKEN_KEY);
  doLogout();
  document.getElementById('login-error').textContent = d.error || 'Sessionen har löpt ut';
  showTab('login');
});

// Re-join after reconnect
socket.on('connect', () => {
  if (currentUser) socket.emit('join', {token: currentUser.token});
});

// ---- Chat input ----
const chatLog  = document.getElementById('chat-log');
const msgInput = document.getElementById('msg-input');
const sendBtn  = document.getElementById('send-btn');
const cmdHint  = document.getElementById('cmd-hint');

msgInput.addEventListener('input', () => {
  const val = msgInput.value;
  sendBtn.disabled = val.trim().length === 0;
  msgInput.style.height = '';
  msgInput.style.height = Math.min(msgInput.scrollHeight, 96) + 'px';
  cmdHint.style.display = val.startsWith('/') ? 'block' : 'none';
});

msgInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    if (!sendBtn.disabled) sendBtn.click();
  }
  if (e.key === 'Escape') cmdHint.style.display = 'none';
});

sendBtn.addEventListener('click', () => {
  const text = msgInput.value.trim();
  if (!text || !currentUser) return;

  const playMatch = text.match(/^\/spela\s+(\S+)/i);
  if (playMatch) {
    const url = playMatch[1];
    if (!/^https?:\/\//i.test(url)) {
      addSystemMsg('Ange en giltig URL: /spela https://...');
    } else {
      socket.emit('play_suggestion', { url });
    }
  } else {
    socket.emit('chat_message', { text });
    addMyMsg(text);
  }

  msgInput.value = '';
  msgInput.style.height = '';
  sendBtn.disabled = true;
  cmdHint.style.display = 'none';
});

// ---- Message renderers ----
function myAvatarHtml() {
  if (currentUser.avatar_url) return `<img src="${currentUser.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
  return escHtml(currentUser.emoji);
}

function addMyMsg(text) {
  const div = document.createElement('div');
  div.className = 'cm mine';
  div.innerHTML = `
    <div class="cm-avatar" style="background:#2563eb22;color:#60a5fa">${myAvatarHtml()}</div>
    <div class="cm-bubble">${_renderContent(text)}</div>`;
  chatLog.appendChild(div);
  chatLog.scrollTop = chatLog.scrollHeight;
}

function otherAvatarHtml(d) {
  if (d.avatar_url) return `<img src="${d.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
  return escHtml(d.emoji || d.name.charAt(0));
}

function addOtherMsg(d) {
  const div = document.createElement('div');
  div.className = 'cm';
  div.innerHTML = `
    <div class="cm-avatar" style="background:${d.color}22;color:${d.color}">${otherAvatarHtml(d)}</div>
    <div>
      <div class="cm-name" style="color:${d.color}">${escHtml(d.name)}</div>
      <div class="cm-bubble">${_renderContent(d.text)}</div>
    </div>`;
  chatLog.appendChild(div);
  chatLog.scrollTop = chatLog.scrollHeight;
}

function _addMySuggestion(url) {
  const div = document.createElement('div');
  div.className = 'cm mine';
  div.innerHTML = `
    <div class="cm-avatar" style="background:#2563eb22;color:#60a5fa">${myAvatarHtml()}</div>
    <div>${_suggestionHtml(url)}</div>`;
  chatLog.appendChild(div);
  chatLog.scrollTop = chatLog.scrollHeight;
}

function _addOtherSuggestion(d) {
  const div = document.createElement('div');
  div.className = 'cm';
  div.innerHTML = `
    <div class="cm-avatar" style="background:${d.color}22;color:${d.color}">${otherAvatarHtml(d)}</div>
    <div>
      <div class="cm-name" style="color:${d.color}">${escHtml(d.name)}</div>
      ${_suggestionHtml(d.text)}
    </div>`;
  chatLog.appendChild(div);
  chatLog.scrollTop = chatLog.scrollHeight;
}

function addSystemMsg(text) {
  const div = document.createElement('div');
  div.className = 'cm system';
  div.innerHTML = `<div class="cm-bubble">${escHtml(text)}</div>`;
  chatLog.appendChild(div);
  chatLog.scrollTop = chatLog.scrollHeight;
}

window.addEventListener('beforeunload', () => socket.disconnect());

// ---- Bifogning ----

const attachBtn   = document.getElementById('attach-btn');
const attachPanel = document.getElementById('attach-panel');
const gifUrlRow   = document.getElementById('gif-url-row');
const gifUrlInput = document.getElementById('gif-url-input');

attachBtn.addEventListener('click', () => {
  const open = attachPanel.style.display !== 'none';
  attachPanel.style.display = open ? 'none' : 'flex';
  gifUrlRow.style.display   = 'none';
  attachBtn.classList.toggle('open', !open);
});

function closeAttachPanel() {
  attachPanel.style.display = 'none';
  gifUrlRow.style.display   = 'none';
  attachBtn.classList.remove('open');
}

function triggerFileInput(mode) {
  closeAttachPanel();
  document.getElementById(mode === 'camera' ? 'file-camera' : 'file-gallery').click();
}

function showGifUrlRow() {
  attachPanel.style.display = 'none';
  gifUrlRow.style.display   = 'flex';
  gifUrlInput.value = '';
  gifUrlInput.focus();
}

document.getElementById('gif-url-send').addEventListener('click', () => {
  const url = gifUrlInput.value.trim();
  if (!url || !/^https?:\/\//i.test(url)) return;
  socket.emit('chat_message', { text: url });
  addMyMsg(url);
  gifUrlInput.value = '';
  gifUrlRow.style.display = 'none';
  msgInput.focus();
});

gifUrlInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') document.getElementById('gif-url-send').click();
  if (e.key === 'Escape') { gifUrlRow.style.display = 'none'; msgInput.focus(); }
});

async function uploadAndSendImage(file) {
  if (!currentUser) return;

  const indicator = document.getElementById('upload-indicator');
  const fill      = document.getElementById('upload-fill');
  const label     = document.getElementById('upload-label');

  indicator.style.display = 'flex';
  fill.style.width = '0%';
  label.textContent = 'Laddar upp...';

  const fd = new FormData();
  fd.append('token', currentUser.token);
  fd.append('image', file);

  const xhr = new XMLHttpRequest();
  xhr.upload.onprogress = (e) => {
    if (e.lengthComputable) fill.style.width = Math.round(e.loaded / e.total * 100) + '%';
  };

  xhr.onload = () => {
    indicator.style.display = 'none';
    if (xhr.status === 200) {
      const d = JSON.parse(xhr.responseText);
      socket.emit('chat_message', { text: d.url });
      addMyMsg(d.url);
    } else {
      addSystemMsg('Uppladdning misslyckades');
    }
  };

  xhr.onerror = () => {
    indicator.style.display = 'none';
    addSystemMsg('Nätverksfel vid uppladdning');
  };

  xhr.open('POST', '/api/chat/image');
  xhr.send(fd);
}

document.getElementById('file-camera').addEventListener('change',  (e) => {
  if (e.target.files[0]) uploadAndSendImage(e.target.files[0]);
  e.target.value = '';
});
document.getElementById('file-gallery').addEventListener('change', (e) => {
  if (e.target.files[0]) uploadAndSendImage(e.target.files[0]);
  e.target.value = '';
});

// Stäng panel vid klick utanför
document.addEventListener('click', (e) => {
  if (!attachBtn.contains(e.target) && !attachPanel.contains(e.target) && !gifUrlRow.contains(e.target)) {
    closeAttachPanel();
  }
});
</script>
</body>
</html>