File size: 13,758 Bytes
1c17d43
 
 
99dfdc4
208a6cb
99dfdc4
 
 
d6edb9a
99dfdc4
 
ce84dc5
1c17d43
 
5b7f9c8
1c17d43
 
 
 
 
 
 
 
208a6cb
1c17d43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93285ba
1c17d43
 
 
 
 
 
 
 
 
 
 
93285ba
73a84fa
93285ba
1c17d43
93285ba
1c17d43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5981e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c17d43
93285ba
 
 
 
 
 
 
 
73a84fa
 
 
 
 
 
 
93285ba
 
 
 
73a84fa
 
 
93285ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c17d43
239f717
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
<!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">

<!-- Für schöne Vorschau beim Teilen -->
<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>

<!-- BANNED SCREEN -->
<div id="banned-screen">
  <div class="banned-icon">🚫</div>
  <div class="banned-title">GEBANNT</div>
  <div class="banned-sub" id="banned-sub"></div>
</div>

<!-- AUTH SCREEN -->
<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>

    <!-- LOGIN -->
    <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>

    <!-- REGISTER -->
    <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>

<!-- MAIN APP -->
<div id="app">

  <!-- TOPBAR -->
  <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>

  <!-- MOBILE SIDEBAR BACKDROP -->
  <div id="sidebar-backdrop" onclick="closeMobileSidebar()" style="display:none"></div>

  <!-- SIDEBAR -->
  <div class="sidebar" id="sidebar">
    <div class="sidebar-scroll">

      <!-- Channels -->
      <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>

      <!-- DMs -->
      <div class="sidebar-section">Direktnachrichten</div>
      <div id="dm-list"></div>

      <div class="sidebar-divider"></div>

      <!-- Members -->
      <div class="sidebar-section">Mitglieder</div>
      <div id="user-list"></div>

    </div>

    <!-- Voice Panel -->
    <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>

  <!-- CHAT AREA -->
  <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>

<!-- CONTEXT MENU -->
<div id="context-menu" style="display:none"></div>

<!-- MODAL CONTAINER -->
<div id="modal-container"></div>

<script>
// ─── DEFAULT DATA (from data.json equivalent) ─────────────────
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>

<!-- Search & Pins modals -->
<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>
// ─── MOBILE SIDEBAR TOGGLE ─────────────────────────────────────
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';
}

// Show/hide hamburger based on screen size
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();

// Close sidebar when a channel is selected on mobile
document.addEventListener('click', function(e) {
  if (!isMobile()) return;
  const item = e.target.closest('.channel-item, .user-item');
  if (item) closeMobileSidebar();
});

// Fix iOS viewport resize when keyboard opens
function fixViewportOnKeyboard() {
  if (!isMobile()) return;
  const chatArea = document.querySelector('.chat-area');
  if (!chatArea) return;
  // Scroll to bottom when keyboard opens
  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>