Ваш идентификаторAI-17A-727F4C
+ ФОТО
Введите имя или нажмите «Войти»
Лента
03.05
Обновление PL‑0 v1.4
02.05
Приказ: внедрение PRISM
30.04
Метрики: ответ ↓23%
28.04
Контрагент: ГК «Спектр»
Поиск
Сотрудник
Алексей Иванов
Документ
Приказ №17
Канал
#rnd-updates
ВЫ
ЧастотаДатаГруппыИмя
🛡P2P
Чаты
Файлы
Каналы
Контрагенты
Аудит
Настройки
AI-17A-727F4C
Файлы
roadmap_prism_v2.pdf2.4 MB
отчёт_аудит_май_2026.docx1.1 MB
презентация_партнёры.pptx5.8 MB
Каналы
#rnd-updates12 участников
#security-alerts8 участников
#general47 участников
Контрагенты
Аудит
Последняя проверка03.05.2026
Целостность журналаOK
Активных сессий4
Настройки
ВЫ
Нажмите для смены фото
ТемаТёмная
УведомленияВсе
ПриватностьКонтакты
ШифрованиеPL-0
Экспорт данных
Редактор таблиц
Профиль пользователя
Должность
Отдел
О себе
Статус
// ==================== PRISM ENGINE v2.0 ==================== // ===== GLOBALS ===== let myProfile = { name: 'Вы', position: 'Сотрудник', department: 'R&D', bio: '', avatar: null, status: 'online', id: 'AI-17A-727F4C' }; let themeDark = true; let currentOrbit = 1, currentSort = 'freq'; let activeChat = null; let curFS = null; let isRecording = false; let mediaRecorder = null; let audioChunks = []; let replyToMsg = null; let editingMsgId = null; // ===== BACKGROUND CANVAS ===== const bgC = document.getElementById('bg-canvas'), bgX = bgC.getContext('2d'); let bgW, bgH; const stars = [], nodes = []; for (let i = 0; i< 180; i++) stars.push({ x: Math.random() * bgW, y: Math.random() * bgH, r: Math.random() * 0.5 + 0.1, a: Math.random() * 0.5 + 0.2, s: Math.random() * 0.002 + 0.001, o: Math.random() * Math.PI * 2 }); for (let i = 0; i< 8; i++) nodes.push({ x: Math.random() * bgW, y: Math.random() * bgH, vx: (Math.random() - 0.5) * 0.04, vy: (Math.random() - 0.5) * 0.04, r: 1.2, a: 0.16 }); function rsz() { bgW = bgC.width = innerWidth; bgH = bgC.height = innerHeight; } window.addEventListener('resize', () => { rsz(); placeNeurons(); setOrbit(currentOrbit); }); rsz(); function drawBg(t) { bgX.clearRect(0, 0, bgW, bgH); for (let i = 0; i< nodes.length; i++) for (let j = i + 1; j< nodes.length; j++) { const d = Math.hypot(nodes[i].x - nodes[j].x, nodes[i].y - nodes[j].y); if (d< bgW * 0.4) { bgX.beginPath(); bgX.moveTo(nodes[i].x, nodes[i].y); bgX.lineTo(nodes[j].x, nodes[j].y); bgX.strokeStyle = `rgba(0,240,255,${(1-d/(bgW*0.4))*0.04})`; bgX.lineWidth = 0.2; bgX.stroke(); } } for (let n of nodes) { n.x += n.vx; n.y += n.vy; if (n.x< 0 || n.x > bgW) n.vx *= -1; if (n.y< 0 || n.y > bgH) n.vy *= -1; bgX.beginPath(); bgX.arc(n.x, n.y, n.r, 0, Math.PI * 2); bgX.fillStyle = `rgba(0,240,255,${n.a})`; bgX.fill(); } for (let s of stars) { const f = Math.sin(t * s.s + s.o) * 0.3 + 0.7; bgX.beginPath(); bgX.arc(s.x, s.y, s.r, 0, Math.PI * 2); bgX.fillStyle = `rgba(255,255,255,${s.a*f})`; bgX.fill(); } requestAnimationFrame(drawBg); } requestAnimationFrame(drawBg); // ===== TOAST ===== function showToast(msg) { const tc = document.getElementById('toastContainer'); const t = document.createElement('div'); t.className = 'toast'; t.textContent = msg; tc.appendChild(t); setTimeout(() => t.remove(), 2800); } // ===== LOCAL STORAGE HELPERS ===== function saveData(key, data) { localStorage.setItem('prism_' + key, JSON.stringify(data)); } function loadData(key, def) { const d = localStorage.getItem('prism_' + key); return d ? JSON.parse(d) : def; } // ===== PROFILE ===== function loadProfile() { const saved = loadData('profile', null); if (saved) { myProfile = saved; applyProfileToUI(); } } function applyProfileToUI() { const sn = document.getElementById('selfNeuron'); if (myProfile.avatar) { sn.innerHTML = ``; } else { sn .textContent = myProfile.name.substring(0, 2).toUpperCase(); } document.getElementById('profileName').value = myProfile.name || ''; document.getElementById('profilePosition').value = myProfile.position || ''; document.getElementById('profileDepartment').value = myProfile.department || ''; document.getElementById('profileBio').value = myProfile.bio || ''; const pap = document.getElementById('profileAvatarPreview'); if (myProfile.avatar) { pap.innerHTML = ``; } else { pap.textContent = myProfile .name.substring(0, 2).toUpperCase(); } document.getElementById('dockUserId').textContent = myProfile.id; const eap = document.getElementById('entryAvatarUpload'); if (myProfile.avatar) { eap.innerHTML = ``; } } function saveProfile() { myProfile.name = document.getElementById('profileName').value || myProfile.name; myProfile.position = document.getElementById('profilePosition').value || myProfile.position; myProfile.department = document.getElementById('profileDepartment').value || myProfile.department; myProfile.bio = document.getElementById('profileBio').value || myProfile.bio; saveData('profile', myProfile); applyProfileToUI(); updateSelfNeuron(); placeNeurons(); showToast('Профиль сохранён'); closeFS(); } function handleProfileAvatar(input) { if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function(e) { myProfile.avatar = e.target.result; document.getElementById('profileAvatarPreview').innerHTML = ``; document.getElementById('entryAvatarUpload').innerHTML = ``; updateSelfNeuron(); saveData('profile', myProfile); }; reader.readAsDataURL(input.files[0]); } } function handleEntryAvatar(input) { if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function(e) { myProfile.avatar = e.target.result; document.getElementById('entryAvatarUpload').innerHTML = ``; }; reader.readAsDataURL(input.files[0]); } } function updateSelfNeuron() { const sn = document.getElementById('selfNeuron'); if (myProfile.avatar) { sn.innerHTML = ``; } else { sn .textContent = myProfile.name.substring(0, 2).toUpperCase(); } } function viewUserProfile(user) { document.getElementById('viewProfileAvatar').innerHTML = user.avatar ? `` : user.av; document.getElementById('viewProfileName').textContent = user.n; document.getElementById('viewProfileId').textContent = user.id; document.getElementById('viewProfilePosition').textContent = user.position || 'Не указана'; document.getElementById('viewProfileDepartment').textContent = user.grp || 'Не указан'; document.getElementById('viewProfileBio').textContent = user.bio || '—'; document.getElementById('viewProfileStatus').textContent = user.on ? '🟢 Онлайн' : '⚫ Офлайн'; openFS('fsUserProfile'); } // ===== CHAT DATA ===== const chatData = [ { n: 'Алексей И.', id: 'AI-17A-727F4C', f: 15, on: true, nt: true, av: 'АИ', orb: 1, grp: 'R&D', date: '2026-05-01', position: 'Ведущий разработчик', bio: 'Специалист по распределённым системам', avatar: null }, { n: 'Мария С.', id: 'AI-17B-8A3D12', f: 12, on: true, nt: false, av: 'МС', orb: 1, grp: 'R&D', date: '2026-04-28', position: 'Senior QA Engineer', bio: 'Автоматизация тестирования', avatar: null }, { n: 'Дмитрий К.', id: 'AI-22C-1F9E07', f: 8, on: false, nt: false, av: 'ДК', orb: 2, grp: 'Security', date: '2026-04-20', position: 'CISO', bio: 'Информационная безопасность', avatar: null }, { n: 'Елена Н.', id: 'AI-17A-4B2C88', f: 6, on: true, nt: false, av: 'ЕН', orb: 2, grp: 'R&D', date: '2026-04-15', position: 'Frontend Developer', bio: 'UI/UX дизайн', avatar: null }, { n: 'Сергей В.', id: 'AI-33D-9E1A55', f: 4, on: true, nt: true, av: 'СВ', orb: 2, grp: 'Security', date: '2026-04-10', position: 'Сетевой администратор', bio: 'Сети и инфраструктура', avatar: null }, { n: 'Анна П.', id: 'AI-17B-2C7F33', f: 3, on: false, nt: false, av: 'АП', orb: 3, grp: 'Management', date: '2026-03-22', position: 'Project Manager', bio: 'Управление проектами', avatar: null }, { n: 'Олег Т.', id: 'AI-22A-5D8E19', f: 2, on: false, nt: false, av: 'ОТ', orb: 3, grp: 'Security', date: '2026-03-15', position: 'Аналитик', bio: 'Анализ угроз', avatar: null }, { n: 'Ирина Г.', id: 'AI-17C-3F6A77', f: 2, on: true, nt: false, av: 'ИГ', orb: 3, grp: 'Management', date: '2026-03-08', position: 'HR Director', bio: 'Управление персоналом', avatar: null }, { n: 'Павел Л.', id: 'AI-33B-7C2D44', f: 1, on: false, nt: false, av: 'ПЛ', orb: 3, grp: 'R&D', date: '2026-02-28', position: 'Backend Developer', bio: 'Rust и высокие нагрузки', avatar: null }, { n: 'Ольга Р.', id: 'AI-22B-1A9F66', f: 1, on: true, nt: false, av: 'ОР', orb: 3, grp: 'Management', date: '2026-02-14', position: 'Financial Director', bio: 'Финансы и бюджетирование', avatar: null }, { n: 'Максим Б.', id: 'AI-17A-8E3C22', f: 1, on: false, nt: false, av: 'МБ', orb: 3, grp: 'Security', date: '2026-02-01', position: 'DevOps Engineer', bio: 'CI/CD и облака', avatar: null }, { n: 'Наталья Ф.', id: 'AI-33C-5D2E11', f: 1, on: true, nt: false, av: 'НФ', orb: 3, grp: 'R&D', date: '2026-01-15', position: 'Data Scientist', bio: 'ML и нейросети', avatar: null }, ]; // ===== MESSAGE STORE ===== let messageStore = loadData('messages', {}); function getChatKey(userId) { return 'chat_' + userId; } function getMessages(userId) { const key = getChatKey(userId); if (!messageStore[key]) messageStore[key] = []; return messageStore[key]; } function saveMessage(userId, msg) { const msgs = getMessages(userId); msgs.push(msg); messageStore[getChatKey(userId)] = msgs; saveData('messages', messageStore); } function updateMessage(userId, msgId, updates) { const msgs = getMessages(userId); const idx = msgs.findIndex(m => m.id === msgId); if (idx >= 0) { Object.assign(msgs[idx], updates); messageStore[getChatKey(userId)] = msgs; saveData('messages', messageStore); } } function deleteMessage(userId, msgId) { const msgs = getMessages(userId); const filtered = msgs.filter(m => m.id !== msgId); messageStore[getChatKey(userId)] = filtered; saveData('messages', messageStore); } // ===== NEURONS ===== const neuronEls = []; const radii = { 1: 145, 2: 245, 3: 355 }; function placeNeurons() { const ctr = document.getElementById('orbitStage'); const cx = innerWidth / 2, cy = innerHeight / 2; const total = { 1: chatData.filter(d => d.orb === 1).length, 2: chatData.filter(d => d.orb === 2).length, 3: chatData.filter(d => d.orb === 3).length }; const counts = { 1: 0, 2: 0, 3: 0 }; neuronEls.length = 0; ctr.querySelectorAll('.chat-neuron').forEach(e => e.remove()); chatData.forEach((c, i) => { const orb = c.orb, idx = counts[orb], angle = (idx / Math.max(total[orb], 1)) * Math.PI * 2 + orb * 0.5, r = radii[orb], x = cx + Math.cos(angle) * r, y = cy + Math.sin(angle) * r * 0.55; counts[orb]++; const el = document.createElement('div'); el.className = 'chat-neuron orbit' + orb + (c.on ? '' : ' offline') + (c.nt ? ' notify' : ''); el.style.left = x + 'px'; el.style.top = y + 'px'; el.style.transform = 'translate(-50%, -50%)'; if (c.avatar) { el.innerHTML = `` + (c.on ? '' : '') + '' + c.n + ''; } else { el.innerHTML = c.av + (c.on ? '' : '') + '' + c.n + ''; } el.addEventListener('click', (e) => { if (e.target.closest('.ctx-menu')) return; openChatFS(c); }); el.addEventListener('contextmenu', (e) => { e.preventDefault(); showContextMenu(e.clientX, e.clientY, [ { label: 'Открыть чат', action: () => openChatFS(c) }, { label: 'Профиль', action: () => viewUserProfile(c) }, { label: 'Верифицировать', action: () => showToast( 'Верификация: отсканируйте QR-код собеседника') }, { label: 'Удалить контакт', action: () => { const idx = chatData.indexOf(c); if (idx >= 0) { chatData.splice(idx, 1); placeNeurons(); setOrbit(currentOrbit); showToast('Контакт удалён'); } }, danger: true }, ]); }); ctr.appendChild(el); c._el = el; neuronEls.push(el); }); applyOrbitVisibility(); updateDockChats(); } function applyOrbitVisibility() { document.querySelectorAll('.orbit-ring').forEach(ring => { const rc = ring.classList.contains('r1') ? 1 : ring.classList.contains('r2') ? 2 : 3; ring.classList.toggle('dimmed', currentOrbit !== 'all' && rc !== currentOrbit); }); neuronEls.forEach(el => { const oc = el.classList.contains('orbit1') ? 1 : el.classList.contains('orbit2') ? 2 : 3; el.classList.toggle('dimmed', currentOrbit !== 'all' && oc !== currentOrbit); el.style.opacity = ''; }); } function setOrbit(orb) { currentOrbit = orb; document.querySelectorAll('.orbit-dot').forEach(d => d.classList.remove('active')); const target = document.querySelector(`.orbit-dot[data-orbit="${orb}"]`); if (target) target.classList.add('active'); document.getElementById('orbitStage').style.transform = `scale(${{1:1,2:0.94,3:0.88,all:0.82}[orb]})`; applyOrbitVisibility(); } window.addEventListener('wheel', e => { if (document.getElementById('chatFS').classList.contains('active')) return; const o = [1, 2, 3, 'all']; const i = o.indexOf(currentOrbit); if (e.deltaY > 10 && i< o.length - 1) setOrbit(o[i + 1]); if (e.deltaY< -10 && i > 0) setOrbit(o[i - 1]); }, { passive: true }); document.querySelectorAll('.orbit-dot').forEach(d => d.addEventListener('click', function(e) { e.stopPropagation(); setOrbit(this.dataset.orbit === 'all' ? 'all' : parseInt(this.dataset.orbit)); })); document.addEventListener('keydown', e => { if (document.getElementById('chatFS').classList.contains('active')) return; if (e.key === '1') setOrbit(1); if (e.key === '2') setOrbit(2); if (e.key === '3') setOrbit(3); if (e.key === '0') setOrbit('all'); const o = [1, 2, 3, 'all'], i = o.indexOf(currentOrbit); if (e.key === 'ArrowLeft' && i > 0) setOrbit(o[i - 1]); if (e.key === 'ArrowRight' && i< o.length - 1) setOrbit(o[i + 1]); }); document.querySelectorAll('.sort-opt').forEach(o => o.addEventListener('click', function() { document.querySelectorAll('.sort-opt').forEach(x => x.classList.remove('active')); this.classList.add('active'); currentSort = this.dataset.sort; sortNeurons(currentSort); })); function sortNeurons(s) { if (s === 'group') { const g = {}; chatData.forEach(c => { if (!g[c.grp]) g[c.grp] = []; g[c.grp].push(c); }); const gn = Object.keys(g); chatData.forEach(c => { c.orb = gn.indexOf(c.grp) === 0 ? 1 : gn.indexOf(c.grp) === 1 ? 2 : 3; }); } else if (s === 'name') { [...chatData].sort((a, b) => a.n.localeCompare(b.n)).forEach((c, i) => { c.orb = i< 4 ? 1 : i< 8 ? 2 : 3; }); } else if (s === 'date') { [...chatData].sort((a, b) => b.date.localeCompare(a.date)).forEach((c, i) => { c.orb = i< 4 ? 1 : i< 8 ? 2 : 3; }); } else { [...chatData].sort((a, b) => b.f - a.f).forEach((c, i) => { c.orb = i< 4 ? 1 : i< 8 ? 2 : 3; }); } placeNeurons(); setOrbit(currentOrbit); } // ===== DOCK CHATS ===== function updateDockChats() { const dd = document.getElementById('dockChatsDD'); dd.innerHTML = chatData.slice(0, 6).map(c => `${c.n}` ).join(''); } // ===== CONTACTS LIST ===== function updateContactsList() { const list = document.getElementById('fsContactsList'); list.innerHTML = chatData.map(c => `
${c.n}${c.id}
` ).join(''); } // ===== SEARCH ===== document.getElementById('centerSearchInput').addEventListener('input', function() { const q = this.value.toLowerCase().trim(); neuronEls.forEach(el => { const lbl = el.querySelector('.neuron-label')?.textContent.toLowerCase() || ''; el.classList.remove('highlighted'); if (!q) { el.style.opacity = ''; el.style.transform = 'translate(-50%,-50%)'; el.style.zIndex = ''; } else if (lbl.includes(q)) { el.classList.add('highlighted'); } else { el .style.opacity = '0.03'; el.style.transform = 'translate(-50%,-50%) scale(0.3)'; } }); }); document.getElementById('panelSearch').addEventListener('input', function() { const q = this.value.toLowerCase().trim(); document.querySelectorAll('.search-result').forEach(r => r.style.display = !q || r.textContent .toLowerCase().includes(q) ? 'block' : 'none'); document.getElementById('centerSearchInput').value = q; document.getElementById('centerSearchInput').dispatchEvent(new Event('input')); }); // ===== CONTEXT MENU ===== let ctxMenuVisible = false; const ctxMenu = document.getElementById('ctxMenu'); function showContextMenu(x, y, items) { ctxMenu.innerHTML = items.map((item, i) => { let html = `
${item.label}
`; return html; }).join(''); ctxMenu.style.left = Math.min(x, innerWidth - 200) + 'px'; ctxMenu.style.top = Math.min(y, innerHeight - items.length * 36 - 20) + 'px'; ctxMenu.classList.add('visible'); ctxMenuVisible = true; ctxMenu.querySelectorAll('.ctx-item').forEach((el, i) => { el.addEventListener('click', () => { hideContextMenu(); items[i].action(); }); }); } function hideContextMenu() { ctxMenu.classList.remove('visible'); ctxMenuVisible = false; } document.addEventListener('click', (e) => { if (!e.target.closest('.ctx-menu') && ctxMenuVisible) hideContextMenu(); }); document.addEventListener('contextmenu', (e) => { if (!e.target.closest('.chat-neuron') && !e.target.closest('.msg-b') && !e.target.closest( '.self-neuron')) { hideContextMenu(); } }); // ===== CHAT ===== function openChatFS(c) { activeChat = c; replyToMsg = null; editingMsgId = null; document.body.classList.add('chat-open'); const avEl = document.getElementById('fsAv'); if (c.avatar) { avEl.innerHTML = ``; } else { avEl .textContent = c.av; } document.getElementById('fsNm').textContent = c.n; document.getElementById('fsId').textContent = c.id; updateShield(c); renderMessages(c); document.getElementById('chatFS').classList.add('active'); document.getElementById('fsInput').focus(); } function updateShield(c) { const shield = document.getElementById('shieldIndicator'); const txt = document.getElementById('shieldText'); if (c.verified) { shield.className = 'shield-indicator'; txt.textContent = 'E2EE ✓'; } else if (c.on) { shield.className = 'shield-indicator warn'; txt.textContent = 'P2P ⚠'; } else { shield.className = 'shield-indicator warn'; txt.textContent = 'P2P'; } } function renderMessages(c) { const tl = document.getElementById('msgTl'); const msgs = getMessages(c.id); tl.innerHTML = msgs.map(m => renderOneMessage(m, c)).join(''); tl.scrollTop = tl.scrollHeight; } function renderOneMessage(m, c) { const cls = m.from === 'me' ? 'mine' : 'theirs'; let content = ''; if (m.replyTo) { const refMsg = getMessages(c.id).find(x => x.id === m.replyTo); if (refMsg) { content += `
↩ ${refMsg.text?.substring(0,40)||'[файл]'}
`; } } if (m.text) content += escapeHtml(m.text); if (m.image) content += `Изображение`; if (m.file) content += `
📎 ${m.file.name} (${m.file.size})
`; if (m.table) { content += ''; if (m.table.headers) content += '' + m.table.headers.map(h => '').join( '') + ''; if (m.table.rows) content += m.table.rows.map(r => '' + r.map(cell => '').join('') + '').join(''); content += '
' + escapeHtml(h) + '
' + escapeHtml(cell) + '
'; } if (m.voice) { content += `
🎤 Голосовое
`; } let reactions = ''; if (m.reactions && Object.keys(m.reactions).length > 0) { reactions = '
' + Object.entries(m.reactions).map(([emoji, users]) => `${emoji} ${users.length}` ).join('') + '
'; } const ts = m.ts || ''; return `
${m.from==='me'?``:''} ${m.from==='me'?``:''}
${content}
${reactions}
${ts}${m.edited?' (изм.)':''}
`; } function escapeHtml(str) { const d = document.createElement('div'); d.textContent = str; return d.innerHTML; } function msgContextMenu(e, msgId) { e.preventDefault(); const msgs = activeChat ? getMessages(activeChat.id) : []; const m = msgs.find(x => x.id === msgId); if (!m) return; const items = [ { label: 'Ответить', action: () => startReply(msgId) }, { label: 'Копировать текст', action: () => { if (m.text) { navigator.clipboard.writeText(m.text) .then(() => showToast('Скопировано')); } } }, { label: 'Переслать', action: () => forwardMsg(msgId) }, ]; if (m.from === 'me') { items.push({ label: 'Редактировать', action: () => startEdit(msgId) }); items.push({ label: 'Удалить', action: () => deleteMsg(msgId), danger: true }); } showContextMenu(e.clientX, e.clientY, items); } function scrollToMsg(msgId) { const el = document.getElementById('msg-' + msgId); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function startReply(msgId) { replyToMsg = msgId; document.getElementById('fsInput').placeholder = 'Ответ на сообщение...'; document.getElementById('fsInput').focus(); showToast('Введите ответ'); } function startEdit(msgId) { const msgs = activeChat ? getMessages(activeChat.id) : []; const m = msgs.find(x => x.id === msgId); if (m && m.text) { editingMsgId = msgId; document.getElementById('fsInput').value = m.text; document.getElementById('fsInput').focus(); showToast('Редактирование сообщения'); } } function deleteMsg(msgId) { if (!activeChat) return; deleteMessage(activeChat.id, msgId); renderMessages(activeChat); showToast('Сообщение удалено'); } function toggleReaction(msgId, emoji) { if (!activeChat) return; const msgs = getMessages(activeChat.id); const m = msgs.find(x => x.id === msgId); if (!m) return; if (!m.reactions) m.reactions = {}; if (!m.reactions[emoji]) m.reactions[emoji] = []; const idx = m.reactions[emoji].indexOf('me'); if (idx >= 0) { m.reactions[emoji].splice(idx, 1); if (m.reactions[emoji].length === 0) delete m.reactions[ emoji]; } else { m.reactions[emoji].push('me'); } updateMessage(activeChat.id, msgId, { reactions: m.reactions }); renderMessages(activeChat); } function forwardMsg(msgId) { showToast('Выберите контакт для пересылки (функция в разработке)'); } function sendFs() { if (!activeChat) return; const inp = document.getElementById('fsInput'), t = inp.value.trim(); if (!t && !editingMsgId) return; const ts = new Date().getHours().toString().padStart(2, '0') + ':' + new Date().getMinutes().toString() .padStart(2, '0'); if (editingMsgId) { updateMessage(activeChat.id, editingMsgId, { text: t, edited: true, ts: ts + ' (изм.)' }); editingMsgId = null; inp.placeholder = 'Сообщение или команда (/help)...'; renderMessages(activeChat); inp.value = ''; inp.focus(); showToast('Сообщение изменено'); return; } if (t.startsWith('/')) { handleCommand(t); inp.value = ''; inp.focus(); return; } const msg = { id: 'msg_' + Date.now(), from: 'me', text: t, ts, replyTo: replyToMsg }; saveMessage(activeChat.id, msg); replyToMsg = null; inp.placeholder = 'Сообщение или команда (/help)...'; renderMessages(activeChat); inp.value = ''; inp.focus(); activeChat.f++; placeNeurons(); setOrbit(currentOrbit); setTimeout(() => { const rts = new Date().getHours().toString().padStart(2, '0') + ':' + new Date().getMinutes() .toString().padStart(2, '0'); const autoReply = { id: 'msg_' + Date.now(), from: 'them', text: 'Принято. ' + activeChat.n + ' получил сообщение.', ts: rts }; saveMessage(activeChat.id, autoReply); renderMessages(activeChat); }, 800 + Math.random() * 1200); } function handleCommand(cmd) { const parts = cmd.split(' '); const command = parts[0].toLowerCase(); switch (command) { case '/help': showToast('/file /burn /verify /export /table /clear'); break; case '/file': triggerFileUpload(); break; case '/burn': const burnMsg = { id: 'msg_' + Date.now(), from: 'me', text: '🔥 Самоуничтожающееся сообщение', ts: new Date().getHours().toString().padStart(2, '0') + ':' + new Date().getMinutes() .toString().padStart(2, '0'), burn: true }; saveMessage(activeChat.id, burnMsg); renderMessages(activeChat); setTimeout(() => { deleteMessage(activeChat.id, burnMsg.id); renderMessages(activeChat); showToast('Сообщение удалено'); }, 5000); break; case '/verify': if (activeChat) { activeChat.verified = true; updateShield(activeChat); showToast('Контакт верифицирован: ' + activeChat.n); } break; case '/export': exportChat(activeChat); break; case '/table': openFS('fsTableEditor'); initSpreadsheet(); break; case '/clear': if (activeChat) { messageStore[getChatKey(activeChat.id)] = []; saveData('messages', messageStore); renderMessages(activeChat); showToast('История очищена'); } break; default: showToast('Неизвестная команда: ' + command); } } function exportChat(c) { if (!c) return; const msgs = getMessages(c.id); let txt = `Чат с ${c.n} (${c.id})\nЭкспорт: ${new Date().toLocaleDateString()}\n---\n`; msgs.forEach(m => { txt += `[${m.ts}] ${m.from==='me'?myProfile.name:c.n}: ${m.text||'[файл]'}\n`; }); const blob = new Blob([txt], { type: 'text/plain' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `chat_${c.id}.txt`; a.click(); showToast('Чат экспортирован'); } function exportAllData() { const all = { profile: myProfile, messages: messageStore, contacts: chatData.map(c => ({ n: c.n, id: c.id, grp: c.grp })) }; const blob = new Blob([JSON.stringify(all, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'prism_export_' + new Date().toISOString().slice(0, 10) + '.json'; a.click(); showToast('Все данные экспортированы'); } document.getElementById('fsInput').addEventListener('keypress', e => { if (e.key === 'Enter') sendFs(); }); function closeChat() { document.body.classList.remove('chat-open'); document.getElementById('chatFS').classList.remove('active'); activeChat = null; replyToMsg = null; editingMsgId = null; document.getElementById('fsInput').placeholder = 'Сообщение или команда (/help)...'; document.getElementById('fsInput').value = ''; } window.closeChat = closeChat; // ===== FILE HANDLING ===== function triggerFileUpload() { document.getElementById('fileInput').click(); } function handleFileAttach(input) { if (!activeChat || !input.files.length) return; Array.from(input.files).forEach(file => { const reader = new FileReader(); reader.onload = function(e) { const ts = new Date().getHours().toString().padStart(2, '0') + ':' + new Date() .getMinutes().toString().padStart(2, '0'); const isImage = file.type.startsWith('image/'); const msg = { id: 'msg_' + Date.now() + Math.random(), from: 'me', ts, text: isImage ? '' : `📎 Файл: ${file.name}`, }; if (isImage) { msg.image = e.target.result; msg.text = ''; } else { msg.file = { name: file.name, size: formatSize(file.size), data: e.target.result }; } saveMessage(activeChat.id, msg); renderMessages(activeChat); }; if (file.type.startsWith('image/')) { reader.readAsDataURL(file); } else { reader.readAsDataURL( file); } }); input.value = ''; showToast('Файлы отправлены'); } function formatSize(bytes) { if (bytes< 1024) return bytes + ' B'; if (bytes< 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(1) + ' MB'; } function downloadFile(name, data) { const a = document.createElement('a'); a.href = data; a.download = name; a.click(); } // ===== VOICE MESSAGES ===== async function toggleVoiceRecord() { if (!activeChat) return; const micBtn = document.getElementById('micBtn'); if (!isRecording) { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = e => audioChunks.push(e.data); mediaRecorder.onstop = () => { const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); const reader = new FileReader(); reader.onload = function(e) { const ts = new Date().getHours().toString().padStart(2, '0') + ':' + new Date() .getMinutes().toString().padStart(2, '0'); const msg = { id: 'msg_' + Date.now(), from: 'me', ts, text: '🎤 Голосовое сообщение', voice: e.target.result }; saveMessage(activeChat.id, msg); renderMessages(activeChat); }; reader.readAsDataURL(audioBlob); stream.getTracks().forEach(t => t.stop()); }; mediaRecorder.start(); isRecording = true; micBtn.classList.add('recording'); micBtn.textContent = '⏹'; showToast('Запись началась...'); } catch (err) { showToast('Нет доступа к микрофону'); } } else { mediaRecorder.stop(); isRecording = false; micBtn.classList.remove('recording'); micBtn.textContent = '🎤'; showToast('Запись сохранена'); } } function playVoice(msgId) { if (!activeChat) return; const msgs = getMessages(activeChat.id); const m = msgs.find(x => x.id === msgId); if (m && m.voice) { const audio = new Audio(m.voice); audio.play(); } } // ===== LIGHTBOX ===== function openLightbox(src) { document.getElementById('lightboxImg').src = src; document.getElementById('lightbox').classList.add('open'); } function closeLightbox() { document.getElementById('lightbox').classList.remove('open'); } // ===== TABLE EDITOR ===== let tableData = { headers: ['Колонка 1', 'Колонка 2', 'Колонка 3'], rows: [ ['', '', ''], ['', '', ''], ['', '', ''] ] }; function initSpreadsheet() { tableData = { headers: ['Колонка 1', 'Колонка 2', 'Колонка 3'], rows: [ ['', '', ''], ['', '', ''], ['', '', ''] ] }; renderSpreadsheet(); } function renderSpreadsheet() { const container = document.getElementById('spreadsheetContainer'); let html = ''; html += '' + tableData.headers.map((h, i) => ``).join( '') + ''; html += tableData.rows.map((row, ri) => '' + row.map((cell, ci) => `` ).join('') + '').join(''); html += '
${escapeHtml(h)}
${escapeHtml(cell)}
'; container.innerHTML = html; } function updateHeader(i, val) { tableData.headers[i] = val; } function updateCell(ri, ci, val) { tableData.rows[ri][ci] = val; } function tableAddRow() { tableData.rows.push(tableData.headers.map(() => '')); renderSpreadsheet(); } function tableAddCol() { tableData.headers.push('Колонка ' + (tableData.headers.length + 1)); tableData.rows.forEach(r => r.push('')); renderSpreadsheet(); } function tableDelRow() { if (tableData.rows.length > 1) { tableData.rows.pop(); renderSpreadsheet(); } } function tableDelCol() { if (tableData.headers.length > 1) { tableData.headers.pop(); tableData.rows.forEach(r => r.pop()); renderSpreadsheet(); } } function tableExportCSV() { let csv = tableData.headers.join(',') + '\n'; csv += tableData.rows.map(r => r.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'table_export.csv'; a.click(); showToast('Таблица экспортирована в CSV'); } function tableImportCSV() { document.getElementById('csvImportInput').click(); } function handleCSVImport(input) { if (!input.files.length) return; const reader = new FileReader(); reader.onload = function(e) { const lines = e.target.result.split('\n').filter(l => l.trim()); if (lines.length > 0) { tableData.headers = lines[0].split(','); tableData.rows = lines.slice(1).map(l => l.split(',')); renderSpreadsheet(); showToast('CSV импортирован'); } }; reader.readAsText(input.files[0]); input.value = ''; } function tableSendToChat() { if (!activeChat) { showToast('Откройте чат для отправки таблицы'); closeFS(); return; } const ts = new Date().getHours().toString().padStart(2, '0') + ':' + new Date().getMinutes().toString() .padStart(2, '0'); const msg = { id: 'msg_' + Date.now(), from: 'me', ts, text: '📊 Таблица', table: { headers: tableData.headers, rows: tableData.rows } }; saveMessage(activeChat.id, msg); renderMessages(activeChat); closeFS(); showToast('Таблица отправлена в чат'); } // ===== FULLSCREEN OVERLAYS ===== function openFS(id) { document.getElementById(id).classList.add('open'); document.body.classList.add('panel-open'); curFS = id; if (id === 'fsContacts') updateContactsList(); if (id === 'fsTableEditor') initSpreadsheet(); } function closeFS() { if (curFS) { document.getElementById(curFS).classList.remove('open'); curFS = null; } document.body.classList.remove('panel-open'); } window.closeFS = closeFS; window.openSettings = () => openFS('fsSettings'); document.querySelectorAll('.dock-i').forEach(item => item.addEventListener('click', function(e) { if (e.target.closest('.dock-dd-i')) return; const t = this.childNodes[0].textContent?.trim(); if (t === 'Файлы') openFS('fsFiles'); if (t === 'Каналы') openFS('fsChannels'); if (t === 'Контрагенты') openFS('fsContacts'); if (t === 'Аудит') openFS('fsAudit'); if (t === 'Настройки') openSettings(); })); document.querySelectorAll('.fs-ov').forEach(ov => ov.addEventListener('click', e => { if (e.target === ov) closeFS(); })); document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeChat(); closeFS(); hideContextMenu(); } }); // ===== ENTRY ===== window.enterSystem = function() { const nameInput = document.getElementById('entrySearch').value.trim(); if (nameInput) myProfile.name = nameInput; if (myProfile.avatar) { document.getElementById('entryAvatarUpload').innerHTML = ``; } document.getElementById('entryOverlay').classList.add('hidden'); document.getElementById('mainScreen').classList.add('visible'); document.getElementById('dockBar').style.opacity = '0.8'; document.getElementById('centerSearch').style.opacity = '1'; document.getElementById('orbitIndicator').style.opacity = '1'; document.getElementById('sortRow').style.opacity = '1'; document.getElementById('entryProfileSetup').classList.add('visible'); saveData('profile', myProfile); applyProfileToUI(); updateSelfNeuron(); placeNeurons(); setOrbit(1); updateDockChats(); updateContactsList(); }; document.getElementById('entrySearch').addEventListener('keypress', e => { if (e.key === 'Enter') enterSystem (); }); // ===== THEME ===== window.toggleTheme = function() { themeDark = !themeDark; document.body.classList.toggle('theme-light', !themeDark); document.getElementById('themeVal').textContent = themeDark ? 'Тёмная' : 'Светлая'; saveData('theme', themeDark); }; // ===== INIT ===== function init() { loadProfile(); const savedTheme = loadData('theme', true); themeDark = savedTheme; if (!themeDark) document.body.classList.add('theme-light'); document.getElementById('themeVal').textContent = themeDark ? 'Тёмная' : 'Светлая'; applyProfileToUI(); updateSelfNeuron(); updateContactsList(); updateDockChats(); if (myProfile.avatar) { document.getElementById('entryAvatarUpload').innerHTML = ``; document.getElementById('entryProfileSetup').classList.add('visible'); } } init(); document.getElementById('selfNeuron').addEventListener('contextmenu', (e) => { e.preventDefault(); showContextMenu(e.clientX, e.clientY, [ { label: 'Профиль', action: () => openSettings() }, { label: 'Статус: Онлайн', action: () => { myProfile.status = 'online'; showToast('Статус: Онлайн'); } }, { label: 'Статус: Занят', action: () => { myProfile.status = 'busy'; showToast('Статус: Занят'); } }, { label: 'Статус: Невидимка', action: () => { myProfile.status = 'invisible'; showToast('Статус: Невидимка'); } }, ]); }); // ===== DRAG & DROP ON CHAT ===== const chatFS = document.getElementById('chatFS'); chatFS.addEventListener('dragover', e => { e.preventDefault(); chatFS.style.border = '2px dashed rgba(0,240,255,0.3)'; }); chatFS.addEventListener('dragleave', () => { chatFS.style.border = ''; }); chatFS.addEventListener('drop', e => { e.preventDefault(); chatFS.style.border = ''; if (!activeChat) return; const files = e.dataTransfer.files; if (files.length) { const dt = new DataTransfer(); Array.from(files).forEach(f => dt.items.add(f)); document.getElementById('fileInput').files = dt.files; handleFileAttach(document.getElementById('fileInput')); } }); console.log('%c🚀 PRISM v2.0 %cEngine Ready', 'color:#00F0FF;font-size:18px;font-family:monospace;', 'color:#fff;font-size:12px;'); console.log('%c🔐 P2P %c| %c🛡 E2EE %c| %c📊 Tables %c| %c🎤 Voice %c| %c📎 Files', 'color:#0f0;', 'color:#fff;', 'color:#0f0;', 'color:#fff;', 'color:#0f0;', 'color:#fff;', 'color:#0f0;', 'color:#fff;', 'color:#0f0;'); console.log('%c/profile %c- управление профилем %c/table %c- редактор таблиц', 'color:#ff0;', 'color:#fff;', 'color:#ff0;', 'color:#fff;');