| | <!DOCTYPE html> |
| | <html lang="de"> |
| | <head> |
| | <meta name="description" content="NoahsChat – Ein Chatroom von NoahsAPP"> |
| | <meta name="keywords" content="NoahsChat, Chat, NoahsAPP, Noahs Chat, Noah, Discord, Roblox, Bann, Wichtig, Help, Support, Hacks, Jura"> |
| |
|
| | |
| | <meta property="og:title" content="NoahsChat"> |
| | <meta property="og:description" content="Ein Chatroom von NoahsAPP (https://noah33565-noahsapp.static.hf.space)"> |
| | <meta property="og:url" content="https://noah33565-chat.static.hf.space"> |
| | |
| | <meta name="google-site-verification" content="ndJbshO4CFOPURJ44uy8W8qBR_JMsq-gWqqqLtFODtc" /> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>NoahsChat / Noahs Chat</title> |
| | <link rel="stylesheet" href="style.css"> |
| | </head> |
| | <body> |
| |
|
| | |
| | <div id="banned-screen"> |
| | <div class="banned-icon">🚫</div> |
| | <div class="banned-title">GEBANNT</div> |
| | <div class="banned-sub" id="banned-sub"></div> |
| | </div> |
| |
|
| | |
| | <div id="auth-screen"> |
| | <div class="auth-box"> |
| | <div class="auth-logo">Noahs<span>Chat</span></div> |
| | <div class="auth-tagline">by NoahsAPP · v2.0</div> |
| |
|
| | <div class="auth-tabs"> |
| | <button class="auth-tab active" onclick="switchTab('login')">Login</button> |
| | <button class="auth-tab" onclick="switchTab('register')">Registrieren</button> |
| | </div> |
| |
|
| | |
| | <div id="tab-login"> |
| | <div class="auth-field"> |
| | <label>Username</label> |
| | <input type="text" id="login-user" placeholder="dein username" autocomplete="off" onkeydown="if(event.key==='Enter')doLogin()"> |
| | </div> |
| | <div class="auth-field"> |
| | <label>Passwort</label> |
| | <input type="password" id="login-pass" placeholder="••••••••" onkeydown="if(event.key==='Enter')doLogin()"> |
| | </div> |
| | <div class="captcha-box"> |
| | <div> |
| | <div style="font-size:.6rem;color:var(--muted);letter-spacing:.15em;text-transform:uppercase;margin-bottom:4px">Anti-Bot</div> |
| | <div class="captcha-question" id="login-q">? + ? = ?</div> |
| | </div> |
| | <input class="captcha-input" type="number" id="login-a" placeholder="?" onkeydown="if(event.key==='Enter')doLogin()"> |
| | <button class="captcha-refresh" onclick="refreshCaptcha('login')" title="Neu">↺</button> |
| | </div> |
| | <button class="auth-btn" onclick="doLogin()">Einloggen ▶</button> |
| | </div> |
| |
|
| | |
| | <div id="tab-register" style="display:none"> |
| | <div class="auth-field"> |
| | <label>Username</label> |
| | <input type="text" id="reg-user" placeholder="wähle einen username" autocomplete="off"> |
| | </div> |
| | <div class="auth-field"> |
| | <label>Passwort</label> |
| | <input type="password" id="reg-pass" placeholder="mindestens 4 Zeichen"> |
| | </div> |
| | <div class="auth-field"> |
| | <label>Passwort bestätigen</label> |
| | <input type="password" id="reg-pass2" placeholder="nochmal eingeben" onkeydown="if(event.key==='Enter')doRegister()"> |
| | </div> |
| | <div class="captcha-box"> |
| | <div> |
| | <div style="font-size:.6rem;color:var(--muted);letter-spacing:.15em;text-transform:uppercase;margin-bottom:4px">Anti-Bot</div> |
| | <div class="captcha-question" id="reg-q">? + ? = ?</div> |
| | </div> |
| | <input class="captcha-input" type="number" id="reg-a" placeholder="?" onkeydown="if(event.key==='Enter')doRegister()"> |
| | <button class="captcha-refresh" onclick="refreshCaptcha('reg')" title="Neu">↺</button> |
| | </div> |
| | <button class="auth-btn" onclick="doRegister()">Account erstellen ▶</button> |
| | </div> |
| |
|
| | <div class="auth-error" id="auth-error"></div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="app"> |
| |
|
| | |
| | <div class="topbar"> |
| | <a href="#" class="topbar-logo">Noahs<span>APP</span></a> |
| | <div class="topbar-sep"></div> |
| | <div class="topbar-channel"> |
| | <span id="topbar-channel-name"># allgemein</span> |
| | </div> |
| | <div class="topbar-actions"> |
| | <button id="mobile-menu-btn" onclick="toggleMobileSidebar()" title="Menü" style="display:none">☰</button> |
| | <button class="icon-btn" onclick="openSettings()" title="Einstellungen">⚙️</button> |
| | <div class="topbar-sep"></div> |
| | <div class="topbar-user"> |
| | <div class="user-avatar-sm" id="topbar-avatar" onclick="openSettings()"></div> |
| | <span id="topbar-username" style="font-weight:700"></span> |
| | <span class="topbar-role-badge" id="topbar-role-badge"></span> |
| | <button class="logout-btn" onclick="doLogout()">Logout</button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="sidebar-backdrop" onclick="closeMobileSidebar()" style="display:none"></div> |
| |
|
| | |
| | <div class="sidebar" id="sidebar"> |
| | <div class="sidebar-scroll"> |
| |
|
| | |
| | <div class="sidebar-section"> |
| | Channels |
| | <span id="online-count" style="color:var(--green);font-family:'Orbitron',monospace">0</span> |
| | </div> |
| | <div id="channel-list"></div> |
| |
|
| | <div class="sidebar-divider"></div> |
| |
|
| | |
| | <div class="sidebar-section">Direktnachrichten</div> |
| | <div id="dm-list"></div> |
| |
|
| | <div class="sidebar-divider"></div> |
| |
|
| | |
| | <div class="sidebar-section">Mitglieder</div> |
| | <div id="user-list"></div> |
| |
|
| | </div> |
| |
|
| | |
| | <div class="voice-panel" id="voice-panel"> |
| | <div class="voice-status-bar"> |
| | <div class="voice-active-dot"></div> |
| | <div class="voice-name" id="voice-channel-name">🔊 Voice</div> |
| | <div class="voice-controls"> |
| | <button class="voice-ctrl" id="voice-mute-btn" onclick="toggleMute()">🎙 Stumm</button> |
| | <button class="voice-ctrl" id="voice-deaf-btn" onclick="toggleDeafen()">🔔 Taub</button> |
| | <button class="voice-ctrl" style="color:var(--danger)" onclick="leaveVoice()">✕</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="chat-area"> |
| | <div class="chat-header"> |
| | <div class="chat-header-info"> |
| | <div class="chat-header-title" id="chat-title">💬 allgemein</div> |
| | <div class="chat-header-sub" id="chat-sub">Allgemeiner Chat für alle</div> |
| | </div> |
| | <div class="chat-header-actions"> |
| | <button class="icon-btn" id="search-btn" title="Suchen" onclick="openSearchModal()">🔍</button> |
| | <button class="icon-btn" id="pins-btn" title="Angepinnte Nachrichten" onclick="openPinsModal()">📌</button> |
| | </div> |
| | </div> |
| |
|
| | <div class="messages" id="messages"> |
| | <div class="empty-chat"> |
| | <div class="empty-chat-icon">💬</div> |
| | <p>Noch keine Nachrichten. Schreib was!</p> |
| | </div> |
| | </div> |
| |
|
| | <div id="timeout-bar" class="timeout-bar" style="display:none"> |
| | ⏱ Du bist stummgeschaltet bis <span id="timeout-until"></span> |
| | </div> |
| |
|
| | <div class="input-area"> |
| | <textarea class="msg-input" id="msg-input" placeholder="Nachricht schreiben..." |
| | rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea> |
| | <button class="send-btn" id="send-btn" onclick="sendMessage()">SEND</button> |
| | </div> |
| | </div> |
| |
|
| | </div> |
| |
|
| | |
| | <div id="context-menu" style="display:none"></div> |
| |
|
| | |
| | <div id="modal-container"></div> |
| |
|
| | <script> |
| | |
| | window.DEFAULT_CHANNELS = [ |
| | { id: 'ankuendigungen', name: 'ankündigungen', icon: '📢', sub: 'Wichtige Ankündigungen', adminOnly: true, category: 'info' }, |
| | { id: 'regeln', name: 'regeln', icon: '📋', sub: 'Serverregeln', readOnly: true, category: 'info' }, |
| | { id: 'allgemein', name: 'allgemein', icon: '💬', sub: 'Allgemeiner Chat für alle', category: 'chat' }, |
| | { id: 'gaming', name: 'gaming', icon: '🎮', sub: 'Alles rund ums Gaming', category: 'chat' }, |
| | { id: 'roblox', name: 'roblox', icon: '🧱', sub: 'Roblox & NoahsHacks', category: 'chat' }, |
| | { id: 'musik', name: 'musik', icon: '🎵', sub: 'Musik & Playlists', category: 'chat' }, |
| | { id: 'memes', name: 'memes', icon: '😂', sub: 'Memes & Witze', category: 'fun' }, |
| | { id: 'off-topic', name: 'off-topic', icon: '💭', sub: 'Alles andere', category: 'fun' }, |
| | { id: 'kunst', name: 'kunst', icon: '🎨', sub: 'Kreative Werke', category: 'fun' }, |
| | { id: 'technik', name: 'technik', icon: '💻', sub: 'Tech & Coding', category: 'fun' }, |
| | { id: 'voicetext', name: 'voice-lounge', icon: '🔊', sub: 'Begleitchat für Voice', category: 'chat' }, |
| | { id: 'moderators', name: 'mod-logs', icon: '🛡', sub: 'Moderationslog', modOnly: true, category: 'staff' }, |
| | { id: 'admin', name: 'admin-bereich', icon: '⚙️', sub: 'Nur für Admins', adminOnly: true, category: 'staff' } |
| | ]; |
| | |
| | window.DEFAULT_AUTOMOD = { |
| | enabled: true, |
| | maxMsgLength: 800, |
| | spamThreshold: 5, |
| | spamWindow: 10000, |
| | bannedWords: ['scheiß', 'hurensohn', 'wichser', 'fick', 'arschloch'], |
| | linkFilter: false, |
| | capsFilter: true, |
| | capsThreshold: 0.7 |
| | }; |
| | </script> |
| |
|
| | |
| | <script> |
| | async function openSearchModal() { |
| | showModal(` |
| | <div class="modal-title">🔍 Nachrichten durchsuchen</div> |
| | <div class="modal-field"> |
| | <label>Suchbegriff</label> |
| | <input type="text" id="search-q" placeholder="Suche im aktuellen Channel..." oninput="liveSearch(this.value)"> |
| | </div> |
| | <div id="search-results" style="max-height:300px;overflow-y:auto;margin-top:10px"></div> |
| | <div class="modal-btns"> |
| | <button class="modal-btn secondary" onclick="clearModal()">Schließen</button> |
| | </div>`); |
| | } |
| | |
| | async function liveSearch(query) { |
| | const results = document.getElementById('search-results'); |
| | if (!query || query.length < 2) { results.innerHTML = ''; return; } |
| | const msgs = (await DB.get('msgs_' + currentChannel)) || []; |
| | const found = msgs.filter(m => !m.system && m.text && m.text.toLowerCase().includes(query.toLowerCase())); |
| | if (!found.length) { results.innerHTML = '<div style="color:var(--muted);text-align:center;padding:12px;font-size:.85rem">Keine Ergebnisse</div>'; return; } |
| | results.innerHTML = found.slice(-20).map(m => ` |
| | <div style="padding:8px 0;border-bottom:1px solid var(--border)"> |
| | <div style="font-size:.72rem;color:var(--muted);font-family:Orbitron,monospace">${m.author} · ${new Date(m.ts).toLocaleDateString('de-DE')}</div> |
| | <div style="font-size:.9rem;color:var(--text)">${escHtml(m.text).replace(new RegExp(escHtml(query),'gi'), s => `<mark style="background:rgba(0,212,255,0.2);color:var(--accent)">${s}</mark>`)}</div> |
| | </div>`).join(''); |
| | } |
| | |
| | async function openPinsModal() { |
| | const pins = (await DB.get('pins')) || []; |
| | const relevant = pins.filter(p => p.channel === currentChannel); |
| | showModal(` |
| | <div class="modal-title">📌 Angepinnte Nachrichten</div> |
| | <div style="max-height:350px;overflow-y:auto"> |
| | ${!relevant.length ? '<div style="color:var(--muted);text-align:center;padding:20px;font-size:.85rem">Keine angepinnten Nachrichten</div>' : |
| | relevant.map(p => ` |
| | <div style="padding:10px 0;border-bottom:1px solid var(--border)"> |
| | <div style="font-size:.7rem;color:var(--muted);font-family:Orbitron,monospace">📌 ${p.author} · angeheftet von ${p.by}</div> |
| | <div style="font-size:.9rem;color:var(--text);margin-top:4px">${escHtml(p.text)}</div> |
| | </div>`).join('')} |
| | </div> |
| | <div class="modal-btns"> |
| | <button class="modal-btn secondary" onclick="clearModal()">Schließen</button> |
| | </div>`); |
| | } |
| | </script> |
| |
|
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/1.5.4/peerjs.min.js"></script> |
| | <script src="app.js"></script> |
| | <script> |
| | |
| | function isMobile() { return window.innerWidth <= 768; } |
| | |
| | function toggleMobileSidebar() { |
| | const sidebar = document.getElementById('sidebar'); |
| | const backdrop = document.getElementById('sidebar-backdrop'); |
| | const open = sidebar.classList.toggle('open'); |
| | if (open) { |
| | backdrop.style.display = 'block'; |
| | backdrop.classList.add('visible'); |
| | } else { |
| | backdrop.classList.remove('visible'); |
| | backdrop.style.display = 'none'; |
| | } |
| | } |
| | |
| | function closeMobileSidebar() { |
| | document.getElementById('sidebar').classList.remove('open'); |
| | const backdrop = document.getElementById('sidebar-backdrop'); |
| | backdrop.classList.remove('visible'); |
| | backdrop.style.display = 'none'; |
| | } |
| | |
| | |
| | function updateMobileUI() { |
| | const btn = document.getElementById('mobile-menu-btn'); |
| | if (btn) btn.style.display = isMobile() ? 'flex' : 'none'; |
| | } |
| | window.addEventListener('resize', updateMobileUI); |
| | document.addEventListener('DOMContentLoaded', updateMobileUI); |
| | updateMobileUI(); |
| | |
| | |
| | document.addEventListener('click', function(e) { |
| | if (!isMobile()) return; |
| | const item = e.target.closest('.channel-item, .user-item'); |
| | if (item) closeMobileSidebar(); |
| | }); |
| | |
| | |
| | function fixViewportOnKeyboard() { |
| | if (!isMobile()) return; |
| | const chatArea = document.querySelector('.chat-area'); |
| | if (!chatArea) return; |
| | |
| | const input = document.getElementById('msg-input'); |
| | if (input) { |
| | input.addEventListener('focus', () => { |
| | setTimeout(() => { |
| | const msgs = document.getElementById('messages'); |
| | if (msgs) msgs.scrollTop = msgs.scrollHeight; |
| | }, 350); |
| | }); |
| | } |
| | } |
| | document.addEventListener('DOMContentLoaded', fixViewportOnKeyboard); |
| | </script> |
| | </body> |
| | </html> |