diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..1af0f4d --- /dev/null +++ b/static/app.js @@ -0,0 +1,411 @@ +let currentUser = null; +let currentPartner = null; +let selectedUserElement = null; +let lastMessageId = 0; +let messagesInterval = null; + +const BASE_PATH = document.querySelector('base')?.getAttribute('href') || '/'; +const API_PREFIX = BASE_PATH.replace(/\/$/, ''); + +// Переключение вкладок логин/регистрация +function switchTab(tab) { + document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.auth-form').forEach(f => f.style.display = 'none'); + + if (tab === 'login') { + document.querySelectorAll('.tab')[0].classList.add('active'); + document.getElementById('login-form').style.display = 'flex'; + } else { + document.querySelectorAll('.tab')[1].classList.add('active'); + document.getElementById('register-form').style.display = 'flex'; + } +} + +// Регистрация +async function register() { + const username = document.getElementById('reg-username').value.trim(); + const password = document.getElementById('reg-password').value.trim(); + + if (!username || !password) { + showAuthError('Заполните все поля'); + return; + } + + try { + const res = await fetch(`${API_PREFIX}/api/register`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({username, password}) + }); + + const data = await res.json(); + + if (res.ok) { + showAuthError(data.message, 'green'); + } else { + showAuthError(data.error); + } + } catch (err) { + showAuthError('Ошибка соединения'); + } +} + +function showAuthError(message, color = 'red') { + const errorEl = document.getElementById('auth-error'); + errorEl.textContent = message; + errorEl.style.color = color; +} + +// Вход +async function login() { + const username = document.getElementById('login-username').value.trim(); + const password = document.getElementById('login-password').value.trim(); + + if (!username || !password) { + showAuthError('Заполните все поля'); + return; + } + + try { + const res = await fetch(`${API_PREFIX}/api/login`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({username, password}) + }); + + const data = await res.json(); + + if (res.ok) { + currentUser = data; + document.getElementById('auth-screen').style.display = 'none'; + document.getElementById('chat-screen').style.display = 'flex'; + document.getElementById('current-username').textContent = `👤 ${data.username}`; + + if (data.is_admin) { + document.getElementById('admin-btn').style.display = 'block'; + } + + // На десктопе показываем сайдбар + if (window.innerWidth > 768) { + document.getElementById('sidebar').classList.remove('hidden'); + } + + loadUsers(); + + // Запускаем проверку новых сообщений + startPolling(); + } else { + showAuthError(data.error); + } + } catch (err) { + showAuthError('Ошибка соединения'); + } +} + +// Загрузка списка пользователей +async function loadUsers() { + if (!currentUser) return; + + try { + const res = await fetch(`${API_PREFIX}/api/users?user_id=${currentUser.user_id}`); + + if (!res.ok) { + console.error('Error loading users'); + return; + } + + const users = await res.json(); + + const usersList = document.getElementById('users-list'); + usersList.innerHTML = ''; + + if (!users || users.length === 0) { + usersList.innerHTML = '
Нет активных пользователей
'; + return; + } + + users.forEach(user => { + const userEl = document.createElement('div'); + userEl.className = 'user-item'; + userEl.innerHTML = `${escapeHtml(user.username)}`; + userEl.onclick = () => selectUser(user, userEl); + + // Если это текущий партнер, подсвечиваем + if (currentPartner && currentPartner.id === user.id) { + userEl.classList.add('active'); + selectedUserElement = userEl; + } + + usersList.appendChild(userEl); + }); + } catch (err) { + console.error('Error loading users:', err); + } +} + +// Выбор собеседника +function selectUser(user, element) { + if (selectedUserElement) { + selectedUserElement.classList.remove('active'); + } + + currentPartner = user; + selectedUserElement = element; + element.classList.add('active'); + + document.getElementById('chat-header').innerHTML = ` + + 💬 ${escapeHtml(user.username)} + `; + document.getElementById('message-input').disabled = false; + document.getElementById('send-btn').disabled = false; + + // Скрываем сайдбар на мобильных + if (window.innerWidth <= 768) { + document.getElementById('sidebar').classList.add('hidden'); + } + + // Сбрасываем ID последнего сообщения для нового партнера + lastMessageId = 0; + + // Загружаем сообщения + loadMessages(true); +} + +// Загрузка сообщений с проверкой новых +async function loadMessages(forceScroll = false) { + if (!currentPartner || !currentUser) return; + + try { + const res = await fetch(`${API_PREFIX}/api/messages?user_id=${currentUser.user_id}&partner_id=${currentPartner.id}`); + const messages = await res.json(); + + if (!messages || messages.length === 0) { + const messagesDiv = document.getElementById('messages'); + messagesDiv.innerHTML = ` +
+
+
💭
+
Нет сообщений
Напишите первое!
+
+
+ `; + return; + } + + // Проверяем, есть ли новые сообщения + const latestMessage = messages[messages.length - 1]; + if (latestMessage.id === lastMessageId && !forceScroll) { + return; // Нет новых сообщений, не обновляем DOM + } + + lastMessageId = latestMessage.id; + + const messagesDiv = document.getElementById('messages'); + + // Проверяем, был ли пользователь внизу + const isAtBottom = messagesDiv.scrollTop + messagesDiv.clientHeight >= messagesDiv.scrollHeight - 30; + + // Обновляем только если есть изменения + messagesDiv.innerHTML = ''; + + messages.forEach(msg => { + const msgEl = document.createElement('div'); + msgEl.className = `message ${msg.from_id === currentUser.user_id ? 'sent' : 'received'}`; + + const time = new Date(msg.timestamp * 1000).toLocaleTimeString('ru-RU', { + hour: '2-digit', + minute: '2-digit' + }); + + msgEl.innerHTML = ` +
+ ${msg.from_id !== currentUser.user_id ? `
${escapeHtml(msg.username)}
` : ''} + ${escapeHtml(msg.text)} +
${time}
+
+ `; + + messagesDiv.appendChild(msgEl); + }); + + // Скроллим вниз только если были внизу или это принудительно + if (isAtBottom || forceScroll) { + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + } catch (err) { + console.error('Error loading messages:', err); + } +} + +// Защита от XSS +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +// Отправка сообщения +async function sendMessage() { + const input = document.getElementById('message-input'); + const text = input.value.trim(); + + if (!text || !currentPartner) return; + + // Блокируем кнопку на время отправки + const sendBtn = document.getElementById('send-btn'); + sendBtn.disabled = true; + input.disabled = true; + + try { + const res = await fetch(`${API_PREFIX}/api/messages`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + from_id: currentUser.user_id, + to_id: currentPartner.id, + text: text + }) + }); + + if (res.ok) { + input.value = ''; + // Принудительно обновляем сообщения + await loadMessages(true); + } + } catch (err) { + console.error('Error sending message:', err); + } finally { + sendBtn.disabled = false; + input.disabled = false; + input.focus(); + } +} + +// Умный поллинг +function startPolling() { + if (messagesInterval) { + clearInterval(messagesInterval); + } + + // Проверяем новые сообщения каждые 2 секунды + messagesInterval = setInterval(() => { + if (currentPartner) { + loadMessages(false); + } + }, 2000); + + // Также проверяем список пользователей раз в 30 секунд + setInterval(() => { + if (currentUser) { + loadUsers(); + } + }, 30000); +} + +// Показать боковую панель на мобильных +function showSidebar() { + if (window.innerWidth <= 768) { + document.getElementById('sidebar').classList.remove('hidden'); + } +} + +// Enter для отправки +document.addEventListener('DOMContentLoaded', () => { + const messageInput = document.getElementById('message-input'); + + messageInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }); + + // Фикс для мобильных устройств - поднятие поля ввода при фокусе + messageInput.addEventListener('focus', () => { + setTimeout(() => { + const messagesDiv = document.getElementById('messages'); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + messageInput.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, 300); + }); + + messageInput.addEventListener('blur', () => { + window.scrollTo(0, 0); + }); +}); + +// Обработка изменения размера окна +let resizeTimeout; +window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + if (window.innerWidth > 768) { + document.getElementById('sidebar').classList.remove('hidden'); + } else if (!currentPartner) { + document.getElementById('sidebar').classList.remove('hidden'); + } + }, 250); +}); + +// Админ панель +async function showAdminPanel() { + const panel = document.getElementById('admin-panel'); + panel.style.display = 'flex'; + + try { + const res = await fetch(`${API_PREFIX}/api/admin/users?admin_id=${currentUser.user_id}`); + const users = await res.json(); + + const adminList = document.getElementById('admin-users-list'); + adminList.innerHTML = '

Все зарегистрированные пользователи:

'; + + users.forEach(user => { + const userEl = document.createElement('div'); + userEl.className = 'admin-user-item'; + + const statusClass = user.active ? 'status-active' : 'status-inactive'; + const statusText = user.active ? 'Активен' : 'Неактивен'; + + userEl.innerHTML = ` +
+ ${escapeHtml(user.username)} ${user.id === currentUser.user_id ? '(вы)' : ''} + ${statusText} +
+
+ ${user.id !== currentUser.user_id ? ` + + + ` : ''} +
+ `; + adminList.appendChild(userEl); + }); + } catch (err) { + console.error('Error loading admin panel:', err); + } +} + +function closeAdminPanel() { + document.getElementById('admin-panel').style.display = 'none'; +} + +async function toggleUser(userId, active) { + try { + await fetch(`${API_PREFIX}/api/admin/toggle`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + admin_id: currentUser.user_id, + user_id: userId, + active: active + }) + }); + + // Обновляем админ-панель и список пользователей + await showAdminPanel(); + await loadUsers(); + } catch (err) { + console.error('Error toggling user:', err); + } +} \ No newline at end of file diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..a691766 --- /dev/null +++ b/static/index.html @@ -0,0 +1,86 @@ + + + + + + + + + Parley Chat + + + + + +
+
+

💬 Parley Chat

+
+ + +
+ +
+ + + +
+ + + +
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/static/parley.service b/static/parley.service new file mode 100644 index 0000000..2cab889 --- /dev/null +++ b/static/parley.service @@ -0,0 +1,22 @@ +[Unit] +Description=Parley Chat Service +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/parley +ExecStart=/opt/parley/parley +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +# Безопасность +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=full +ProtectHome=yes + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..edcb07f --- /dev/null +++ b/static/style.css @@ -0,0 +1,810 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #0084ff; + --primary-hover: #0073e6; + --bg-color: #f0f2f5; + --white: #ffffff; + --border-color: #e0e0e0; + --text-primary: #1a1a1a; + --text-secondary: #65676b; + --message-sent: #0084ff; + --message-received: #ffffff; + --sidebar-width: 320px; + --header-height: 60px; + --input-height: 70px; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: var(--bg-color); + height: 100vh; + height: 100dvh; + overflow: hidden; + color: var(--text-primary); +} + +/* Auth Screen */ +.auth-screen { + height: 100vh; + height: 100dvh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; +} + +.auth-container { + background: var(--white); + border-radius: 16px; + padding: 40px; + width: 100%; + max-width: 420px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.auth-container h2 { + text-align: center; + margin-bottom: 30px; + color: var(--text-primary); + font-size: 24px; + font-weight: 700; +} + +.tabs { + display: flex; + gap: 10px; + margin-bottom: 25px; +} + +.tab { + flex: 1; + padding: 12px; + border: 2px solid #e0e0e0; + background: transparent; + cursor: pointer; + border-radius: 10px; + font-size: 15px; + font-weight: 500; + color: var(--text-secondary); + transition: all 0.3s; +} + +.tab:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +.tab.active { + background: var(--primary-color); + color: var(--white); + border-color: var(--primary-color); +} + +.auth-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.auth-form input { + padding: 14px 16px; + border: 2px solid #e8e8e8; + border-radius: 10px; + font-size: 16px; + transition: all 0.3s; + outline: none; +} + +.auth-form input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(0, 132, 255, 0.1); +} + +.auth-form button { + padding: 14px; + background: var(--primary-color); + color: var(--white); + border: none; + border-radius: 10px; + cursor: pointer; + font-size: 16px; + font-weight: 600; + transition: all 0.3s; + margin-top: 5px; +} + +.auth-form button:hover { + background: var(--primary-hover); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 132, 255, 0.4); +} + +.auth-form button:active { + transform: translateY(0); +} + +.auth-error { + margin-top: 15px; + text-align: center; + font-size: 14px; + min-height: 20px; +} + +/* Chat Layout */ +.chat-screen { + height: 100vh; + height: 100dvh; + display: flex; + justify-content: center; + align-items: center; + background: #e8eaed; + padding: 0; +} + +.container { + display: flex; + height: 100vh; + height: 100dvh; + width: 100%; + background: var(--white); +} + +/* Sidebar */ +.sidebar { + width: var(--sidebar-width); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + background: #f8f9fa; + flex-shrink: 0; +} + +.sidebar-header { + height: var(--header-height); + padding: 0 20px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-color); + font-weight: 600; + font-size: 16px; + background: var(--white); +} + +#current-username { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#admin-btn { + background: transparent; + border: none; + font-size: 20px; + cursor: pointer; + padding: 5px 10px; + border-radius: 8px; + transition: background 0.2s; +} + +#admin-btn:hover { + background: #e8e8e8; +} + +.users-list { + flex: 1; + overflow-y: auto; + overflow-x: hidden; +} + +.users-list::-webkit-scrollbar { + width: 6px; +} + +.users-list::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 3px; +} + +.user-item { + padding: 15px 20px; + cursor: pointer; + border-bottom: 1px solid #f0f0f0; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 12px; + font-size: 15px; +} + +.user-item:hover { + background: #e8eaed; +} + +.user-item.active { + background: #e7f3ff; + border-left: 3px solid var(--primary-color); + padding-left: 17px; +} + +.user-item .status { + width: 10px; + height: 10px; + border-radius: 50%; + background: #31a24c; + flex-shrink: 0; + box-shadow: 0 0 0 3px rgba(49, 162, 76, 0.2); +} + +.user-item .username { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Chat Area */ +.chat-area { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + background: var(--white); +} + +.chat-header { + height: var(--header-height); + padding: 0 20px; + display: flex; + align-items: center; + border-bottom: 1px solid var(--border-color); + font-weight: 600; + font-size: 16px; + background: var(--white); + flex-shrink: 0; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 20px; + background: #f5f5f5; + display: flex; + flex-direction: column; + gap: 8px; +} + +.messages::-webkit-scrollbar { + width: 6px; +} + +.messages::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 3px; +} + +.message { + display: flex; + flex-direction: column; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.message.sent { + align-items: flex-end; +} + +.message.received { + align-items: flex-start; +} + +.message-content { + max-width: 65%; + padding: 10px 16px; + border-radius: 18px; + word-wrap: break-word; + position: relative; + line-height: 1.4; + font-size: 15px; +} + +.message.sent .message-content { + background: var(--message-sent); + color: var(--white); + border-bottom-right-radius: 4px; +} + +.message.received .message-content { + background: var(--message-received); + color: var(--text-primary); + border-bottom-left-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.message-username { + font-size: 12px; + color: #65676b; + margin-bottom: 4px; + font-weight: 600; +} + +.message-time { + font-size: 11px; + opacity: 0.7; + margin-top: 4px; + text-align: right; +} + +.message.sent .message-time { + color: rgba(255, 255, 255, 0.8); +} + +.message.received .message-time { + color: #65676b; +} + +/* Input Area */ +.message-input { + height: var(--input-height); + padding: 10px 20px; + background: var(--white); + border-top: 1px solid var(--border-color); + display: flex; + gap: 10px; + align-items: center; + flex-shrink: 0; + position: sticky; + bottom: 0; + z-index: 5; +} + +.message-input input { + flex: 1; + padding: 12px 20px; + border: 2px solid #e8e8e8; + border-radius: 24px; + font-size: 15px; + outline: none; + transition: all 0.3s; + background: #f0f2f5; +} + +.message-input input:focus { + border-color: var(--primary-color); + background: var(--white); + box-shadow: 0 0 0 3px rgba(0, 132, 255, 0.1); +} + +.message-input input:disabled { + background: #f5f5f5; + cursor: not-allowed; +} + +#send-btn { + width: 44px; + height: 44px; + border-radius: 50%; + padding: 0; + font-size: 20px; + background: var(--primary-color); + color: var(--white); + border: none; + cursor: pointer; + transition: all 0.3s; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +#send-btn:hover:not(:disabled) { + background: var(--primary-hover); + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0, 132, 255, 0.4); +} + +#send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Empty state */ +.empty-chat { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #65676b; + font-size: 18px; +} + +.empty-chat-icon { + font-size: 64px; + margin-bottom: 20px; + opacity: 0.3; +} + +.empty-chat-text { + text-align: center; +} + +/* Admin Panel Modal */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + height: 100dvh; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + animation: fadeIn 0.3s; + backdrop-filter: blur(5px); +} + +.modal-content { + background: var(--white); + border-radius: 16px; + padding: 30px; + max-width: 500px; + width: 90%; + max-height: 80vh; + max-height: 80dvh; + overflow-y: auto; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.modal-content h3 { + margin-bottom: 20px; + font-size: 20px; + color: var(--text-primary); +} + +.admin-user-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + border-bottom: 1px solid #f0f0f0; + gap: 10px; + flex-wrap: wrap; +} + +.admin-user-item:last-child { + border-bottom: none; +} + +.admin-user-item .user-info { + display: flex; + align-items: center; + gap: 8px; +} + +.admin-user-item .status-badge { + font-size: 12px; + padding: 4px 10px; + border-radius: 12px; + font-weight: 600; +} + +.status-active { + background: #e8f5e9; + color: #2e7d32; +} + +.status-inactive { + background: #fce4ec; + color: #c62828; +} + +.admin-actions { + display: flex; + gap: 8px; +} + +.toggle-btn { + padding: 8px 16px; + border: none; + border-radius: 8px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; +} + +.toggle-btn.activate { + background: #4caf50; + color: white; +} + +.toggle-btn.activate:hover { + background: #388e3c; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(76, 175, 80, 0.4); +} + +.toggle-btn.deactivate { + background: #f44336; + color: white; +} + +.toggle-btn.deactivate:hover { + background: #d32f2f; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4); +} + +.close-btn { + margin-top: 20px; + width: 100%; + padding: 12px; + background: #f0f2f5; + border: none; + border-radius: 10px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; +} + +.close-btn:hover { + background: #e4e6e9; +} + +/* Mobile Back Button */ +.mobile-back-btn { + display: none; + margin-right: 15px; + font-size: 20px; + cursor: pointer; + padding: 5px; + border-radius: 8px; + transition: background 0.2s; +} + +.mobile-back-btn:hover { + background: #e8e8e8; +} + +/* Desktop styles (≥ 769px) */ +@media (min-width: 769px) { + .container { + max-width: 1400px; + height: calc(100vh - 40px); + height: calc(100dvh - 40px); + margin: 20px auto; + border-radius: 12px; + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); + overflow: hidden; + } + + .chat-screen { + padding: 20px; + } + + .message-content { + max-width: 55%; + } + + .empty-chat { + display: flex; + flex-direction: column; + } + + .message-input { + position: static; + } +} + +/* Tablet styles (481px - 768px) */ +@media (max-width: 768px) { + .container { + position: relative; + } + + .sidebar { + width: 100%; + position: absolute; + left: 0; + top: 0; + height: 100%; + z-index: 10; + transition: transform 0.3s ease; + padding-bottom: env(safe-area-inset-bottom); + } + + .sidebar.hidden { + transform: translateX(-100%); + } + + .chat-area { + width: 100%; + } + + .mobile-back-btn { + display: block; + } + + .chat-header { + cursor: default; + display: flex; + align-items: center; + } + + .message-content { + max-width: 80%; + } + + .message-input { + padding: 10px 15px 20px 15px; + padding-bottom: max(20px, env(safe-area-inset-bottom)); + } + + .users-list { + padding-bottom: env(safe-area-inset-bottom); + } +} + +/* Mobile styles (≤ 480px) */ +@media (max-width: 480px) { + .auth-container { + padding: 25px 20px; + max-height: 90dvh; + overflow-y: auto; + } + + .auth-container h2 { + font-size: 20px; + } + + .tab { + padding: 10px; + font-size: 14px; + } + + .auth-form input { + padding: 12px 14px; + font-size: 16px; + } + + .auth-form button { + padding: 12px; + font-size: 15px; + } + + .sidebar-header { + padding: 0 15px; + } + + .user-item { + padding: 12px 15px; + font-size: 14px; + } + + .chat-header { + padding: 0 15px; + } + + .messages { + padding: 15px; + gap: 6px; + } + + .message-content { + max-width: 85%; + padding: 8px 12px; + font-size: 14px; + } + + .message-input { + height: auto; + min-height: 60px; + padding: 8px 12px 15px 12px; + padding-bottom: max(15px, env(safe-area-inset-bottom)); + gap: 8px; + background: var(--white); + border-top: 1px solid var(--border-color); + position: sticky; + bottom: 0; + z-index: 5; + } + + .message-input input { + padding: 10px 16px; + font-size: 16px; + } + + #send-btn { + width: 40px; + height: 40px; + font-size: 18px; + } + + .modal-content { + padding: 20px; + max-height: 80dvh; + } + + .admin-user-item { + padding: 12px; + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .admin-actions { + width: 100%; + justify-content: flex-end; + } + + .toggle-btn { + padding: 6px 12px; + font-size: 12px; + } +} + +/* Landscape mode for mobile */ +@media (max-height: 500px) and (orientation: landscape) { + .auth-container { + padding: 20px; + margin: 10px; + max-height: 100dvh; + } + + .auth-container h2 { + margin-bottom: 15px; + font-size: 18px; + } + + .tabs { + margin-bottom: 15px; + } + + .auth-form { + gap: 10px; + } + + .message-content { + max-width: 70%; + } + + .message-input { + min-height: 50px; + padding: 5px 15px 10px 15px; + } +} + +/* Для очень маленьких экранов */ +@media (max-width: 320px) { + .message-input { + padding: 5px 8px 12px 8px; + } + + .message-input input { + padding: 8px 12px; + font-size: 14px; + } + + .message-content { + max-width: 90%; + font-size: 13px; + } +} \ No newline at end of file