Files
Haven/public/app.html
T
ancsemi 96c825bdb1 fix: avatar upload - replace label-for-input with button to prevent double-trigger
The <label for='avatar-file-input'> elements created a native label-input
association. In many browsers, e.preventDefault() on label click does NOT
reliably prevent the native for-attribute behavior, causing both the native
activation AND the JS fileInput.click() to fire simultaneously  which
opens the file dialog twice and immediately cancels it.

Fix: Changed <label> to <button type='button'> and removed the for
attribute entirely. The JS click handler is now the sole mechanism for
opening the file dialog, eliminating the conflict.
2026-02-13 22:37:42 -05:00

676 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="#1a1a2e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<title>Haven</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cG9seWdvbiBwb2ludHM9IjUwLDMgOTMsMjggOTMsNzIgNTAsOTcgNyw3MiA3LDI4IiBmaWxsPSJub25lIiBzdHJva2U9IiM2YjRmZGIiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjx0ZXh0IHg9IjUwIiB5PSI2MyIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmIiBmb250LXdlaWdodD0iYm9sZCIgZm9udC1zaXplPSIzOCIgZmlsbD0iIzZiNGZkYiIgb3BhY2l0eT0iMC45NSI+SDwvdGV4dD48L3N2Zz4=">
<link rel="stylesheet" href="/css/style.css">
<script src="/js/theme-init.js"></script>
</head>
<body>
<div id="app">
<div id="app-body">
<!-- ─── Server Bar (far left — like Discord) ─────── -->
<nav class="server-bar" id="server-bar">
<div class="server-icon home active" id="home-server" title="This Server">
<span class="server-icon-text">⬡</span>
<span class="server-status-dot online"></span>
</div>
<div class="server-separator"></div>
<div id="server-list"></div>
<button class="server-icon add-server" id="add-server-btn" title="Add Server">
<span class="server-icon-text">+</span>
</button>
</nav>
<!-- ─── Left Sidebar ────────────────────────────── -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="brand">
<span class="logo-sm">⬡</span>
<span class="brand-text">HAVEN</span>
</div>
<div class="user-bar">
<span class="led on" id="connection-led" title="Server connection"></span>
<div class="user-names">
<span class="current-user" id="current-user"></span>
<span class="login-name" id="login-name" title="Your login username"></span>
</div>
<button id="rename-btn" class="icon-btn small" title="Change display name">✏️</button>
<button id="logout-btn" class="icon-btn" title="Logout">⏻</button>
</div>
</div>
<div class="sidebar-section">
<h5 class="section-label">Join a Channel</h5>
<div class="input-row">
<input type="text" id="channel-code-input"
placeholder="Enter code..." maxlength="8" spellcheck="false">
<button id="join-channel-btn" class="btn-sm">Join</button>
</div>
</div>
<div class="sidebar-section" id="admin-controls" style="display:none">
<h5 class="section-label">Create Channel</h5>
<div class="input-row">
<input type="text" id="new-channel-name"
placeholder="Channel name..." maxlength="50">
<button id="create-channel-btn" class="btn-sm btn-accent">+</button>
</div>
</div>
<div class="sidebar-section channel-section">
<h5 class="section-label">Channels</h5>
<div class="channel-list" id="channel-list"></div>
</div>
<!-- Pinned bottom area (never pushed off screen) -->
<div class="sidebar-bottom">
<!-- Theme popup (floats above bottom bar) -->
<div id="theme-popup" class="theme-popup" style="display:none">
<div class="theme-popup-header">
<span class="theme-popup-title">Theme</span>
<button id="theme-popup-close" class="icon-btn small" title="Close">&times;</button>
</div>
<div class="theme-selector" id="theme-selector">
<button class="theme-btn active" data-theme="haven" title="Haven"><span class="theme-icon">⬡</span></button>
<button class="theme-btn" data-theme="discord" title="Discord"><span class="theme-icon">🎮</span></button>
<button class="theme-btn" data-theme="matrix" title="Matrix"><span class="theme-icon">Ⅿ</span></button>
<button class="theme-btn" data-theme="tron" title="Tron"><span class="theme-icon">◈</span></button>
<button class="theme-btn" data-theme="halo" title="HALO"><span class="theme-icon">⌁</span></button>
<button class="theme-btn" data-theme="lotr" title="LoTR"><span class="theme-icon">⚜</span></button>
<button class="theme-btn" data-theme="cyberpunk" title="Cyberpunk"><span class="theme-icon">⚡</span></button>
<button class="theme-btn" data-theme="nord" title="Nord"><span class="theme-icon">❄</span></button>
<button class="theme-btn" data-theme="dracula" title="Dracula"><span class="theme-icon">🧛</span></button>
<button class="theme-btn" data-theme="bloodborne" title="Bloodborne"><span class="theme-icon">🩸</span></button>
<button class="theme-btn" data-theme="darksouls" title="Dark Souls"><span class="theme-icon">🔥</span></button>
<button class="theme-btn" data-theme="eldenring" title="Elden Ring"><span class="theme-icon">💍</span></button>
<button class="theme-btn" data-theme="ice" title="Ice"><span class="theme-icon">🧊</span></button>
<button class="theme-btn" data-theme="abyss" title="Abyss"><span class="theme-icon">🌊</span></button>
<button class="theme-btn" data-theme="minecraft" title="Minecraft"><span class="theme-icon">⛏️</span></button>
<button class="theme-btn" data-theme="ffx" title="Final Fantasy X"><span class="theme-icon">⚔️</span></button>
<button class="theme-btn" data-theme="zelda" title="Zelda"><span class="theme-icon">🗡️</span></button>
<button class="theme-btn" data-theme="fallout" title="Fallout (PIP Boy)"><span class="theme-icon">☢️</span></button>
<button class="theme-btn" data-theme="crt" title="CRT"><span class="theme-icon">📺</span></button>
<button class="theme-btn" data-theme="win95" title="Windows 95"><span class="theme-icon">🪟</span></button>
<button class="theme-btn" data-theme="custom" title="Custom"><span class="theme-icon" id="custom-theme-swatch">🎨</span></button>
<button class="theme-btn" data-theme="rgb" title="RGB"><span class="theme-icon">🌈</span></button>
</div>
<!-- Custom theme triangle picker -->
<div id="custom-theme-editor" style="display:none">
<canvas id="custom-hue-bar" width="206" height="18"></canvas>
<canvas id="custom-triangle" width="206" height="180"></canvas>
</div>
<!-- RGB theme controls -->
<div id="rgb-theme-editor" class="rgb-theme-editor" style="display:none">
<label class="rgb-slider-row">
<span class="rgb-slider-label">Speed</span>
<input type="range" id="rgb-speed-slider" min="1" max="100" value="30" class="slider-sm rgb-slider">
</label>
<label class="rgb-slider-row">
<span class="rgb-slider-label">Vibrancy</span>
<input type="range" id="rgb-vibrancy-slider" min="10" max="100" value="75" class="slider-sm rgb-slider">
</label>
</div>
<!-- Effect speed slider (dynamic-effect themes) -->
<div id="effect-speed-editor" class="rgb-theme-editor" style="display:none">
<label class="rgb-slider-row">
<span class="rgb-slider-label">Effect Speed</span>
<input type="range" id="effect-speed-slider" min="10" max="200" value="100" class="slider-sm rgb-slider">
</label>
</div>
</div>
<!-- Bottom bar -->
<div class="sidebar-bottom-bar">
<button id="theme-popup-toggle" class="sidebar-bottom-btn" title="Themes">🎨</button>
<button class="sidebar-bottom-btn" id="play-flappy-btn" title="Shippy Container">🚢</button>
<div id="voice-bar" class="voice-bar" style="display:none"></div>
</div>
</div>
</aside>
<!-- ─── Main Content ────────────────────────────── -->
<main class="main">
<header class="channel-header">
<button id="mobile-menu-btn" class="mobile-menu-btn" title="Menu" aria-label="Menu">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
<div class="channel-info">
<h3 id="channel-header-name">Select a channel</h3>
<span class="channel-code-tag" id="channel-code-display" title="Channel code"></span>
<button id="copy-code-btn" class="icon-btn small" title="Copy code"
style="display:none">📋</button>
</div>
<!-- Left-side utility buttons (next to channel name) -->
<div class="header-left-actions">
<button id="search-toggle-btn" class="icon-btn small" title="Search messages (Ctrl+F)" style="display:none">🔍</button>
<button id="pinned-toggle-btn" class="icon-btn small" title="Pinned messages" style="display:none">📌</button>
<div class="search-container" id="search-container" style="display:none">
<input type="text" id="search-input" class="search-input" placeholder="Search messages..." maxlength="100">
<button id="search-close-btn" class="icon-btn small" title="Close search">&times;</button>
</div>
</div>
<!-- Spacer pushes voice controls to the right -->
<div style="flex:1"></div>
<!-- Right-side: dynamic indicators, then voice (always furthest right) -->
<div class="voice-controls">
<button id="mobile-users-btn" class="mobile-users-btn" title="Members" aria-label="Members">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
</button>
<!-- Dynamic indicators (music, streams) inject here via JS -->
<button id="voice-join-btn" class="btn-voice" style="display:none">
🎤 Join Voice
</button>
<div class="voice-dropdown" style="position:relative">
<button id="voice-dropdown-toggle" class="btn-voice voice-active-btn" style="display:none" title="Voice Controls">
🎤 <span class="voice-label-text">Voice </span>▾
</button>
<div id="voice-dropdown-panel" class="voice-dropdown-panel" style="display:none">
<button id="voice-mute-btn" class="btn-voice">🔇 Mute</button>
<button id="voice-deafen-btn" class="btn-voice">🔇 Deafen</button>
<button id="screen-share-btn" class="btn-voice" title="Share Screen">🖥️ Share</button>
<button id="music-share-btn" class="btn-voice" title="Listen Together">🎵 Music</button>
<div class="voice-ns-wrap">
<label class="ns-label" title="Noise suppression sensitivity">🤫</label>
<input type="range" id="voice-ns-slider" class="ns-slider" min="0" max="100" value="10" title="Noise gate: 0 = off, 100 = aggressive">
</div>
</div>
</div>
<button id="voice-leave-btn" class="btn-voice btn-danger" style="display:none">
✕
</button>
</div>
</header>
<!-- Welcome Screen -->
<div id="no-channel-msg" class="welcome-screen">
<div class="welcome-content">
<div class="welcome-icon">⬡</div>
<h2>Welcome to Haven</h2>
<p>Join a channel with a code from your friends,<br>
or create one if you're the admin.</p>
</div>
</div>
<!-- Chat Area -->
<div id="message-area" class="message-area" style="display:none">
<!-- Screen Share Viewer (multi-stream) -->
<div id="screen-share-container" class="screen-share-container" style="display:none">
<div class="screen-share-header">
<span id="screen-share-label">🖥️ Streams</span>
<div class="screen-share-header-controls">
<label class="stream-size-label" title="Stream display size">
🖥️
<input type="range" id="stream-size-slider" class="stream-size-slider" min="20" max="90" value="50">
</label>
<button id="screen-share-minimize" class="icon-btn small" title="Minimize streams">─</button>
<button id="screen-share-close" class="icon-btn small" title="Close streams">✕</button>
</div>
</div>
<div id="screen-share-grid" class="screen-share-grid"></div>
</div>
<!-- Music Player Panel -->
<div id="music-panel" class="music-panel" style="display:none">
<div class="music-panel-header">
<div class="music-panel-header-left">
<button id="music-popout-btn" class="icon-btn small" title="Pop out player">⧉</button>
<span id="music-panel-label">🎵 Listening Together</span>
</div>
<div class="music-panel-controls">
<button id="music-play-pause-btn" class="icon-btn small" title="Play/Pause">⏸</button>
<button id="music-mute-btn" class="icon-btn small" title="Mute music">🔊</button>
<input type="range" id="music-volume-slider" class="stream-vol-slider" min="0" max="100" value="80" title="Music volume">
<button id="music-close-btn" class="icon-btn small" title="Minimize">─</button>
<button id="music-stop-btn" class="icon-btn small" title="Close / stop music">✕</button>
</div>
</div>
<div id="music-embed-container" class="music-embed-container"></div>
</div>
<div id="search-results-panel" class="search-results-panel" style="display:none">
<div class="search-results-header">
<span id="search-results-count">Results</span>
<button id="search-results-close" class="icon-btn small">&times;</button>
</div>
<div id="search-results-list" class="search-results-list"></div>
</div>
<div id="pinned-panel" class="pinned-panel" style="display:none">
<div class="pinned-panel-header">
<span id="pinned-count">📌 Pinned Messages</span>
<button id="pinned-close" class="icon-btn small">&times;</button>
</div>
<div id="pinned-list" class="pinned-list"></div>
</div>
<div class="messages" id="messages"></div>
<div class="typing-indicator" id="typing-indicator"></div>
<div id="reply-bar" class="reply-bar" style="display:none">
<span class="reply-bar-text" id="reply-preview-text"></span>
<button class="reply-bar-close" id="reply-close-btn" title="Cancel reply">&times;</button>
</div>
<div class="message-input-area">
<button id="upload-btn" class="btn-upload" title="Upload Image">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<path d="M21 15l-5-5L5 21"/>
</svg>
</button>
<button id="emoji-btn" class="btn-emoji" title="Emoji">😀</button>
<div id="emoji-picker" class="emoji-picker" style="display:none"></div>
<button id="gif-btn" class="btn-gif" title="Search GIFs">GIF</button>
<div id="gif-picker" class="gif-picker" style="display:none">
<div class="gif-picker-header">
<input type="text" id="gif-search-input" placeholder="Search GIPHY..." maxlength="100" autocomplete="off">
</div>
<div class="gif-picker-grid" id="gif-grid"></div>
<div class="gif-picker-footer">Powered by GIPHY</div>
</div>
<div id="mention-dropdown" class="mention-dropdown" style="display:none"></div>
<div id="slash-dropdown" class="slash-dropdown" style="display:none"></div>
<textarea id="message-input" placeholder="Type a message... (/ for commands)"
rows="1" maxlength="2000"></textarea>
<button id="send-btn" class="btn-send" title="Send">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
</svg>
</button>
</div>
<input type="file" id="file-input" accept="image/jpeg,image/png,image/gif,image/webp" style="display:none">
</div>
</main>
<!-- ─── Right Sidebar ───────────────────────────── -->
<aside class="right-sidebar" id="right-sidebar">
<div class="panel">
<h5 class="panel-title">🔊 Voice</h5>
<button id="voice-join-mobile" class="btn-voice mobile-voice-join" style="display:none">🎤 Join Voice</button>
<div id="voice-users" class="user-list">
<p class="muted-text">No one in voice</p>
</div>
</div>
<div class="panel">
<h5 class="panel-title">👥 Online</h5>
<div id="online-users" class="user-list">
<p class="muted-text">Select a channel</p>
</div>
</div>
<div class="panel sidebar-settings-panel">
<button class="btn-settings-popout" id="open-settings-btn" title="Settings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
<span>Settings</span>
</button>
</div>
</aside>
<!-- Mobile overlay (click to close sidebars) -->
<div id="mobile-overlay" class="mobile-overlay"></div>
</div>
<!-- ─── Status Bar (bottom) ───────────────────────── -->
<div class="status-bar" id="status-bar">
<div class="status-item">
<span class="led on" id="status-server-led"></span>
<span class="label">Server</span>
<span class="value" id="status-server-text">Connected</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="led off" id="status-voice-led"></span>
<span class="label">Voice</span>
<span class="value" id="status-voice-text">Off</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="label">Ping</span>
<span class="value" id="status-ping">--</span>
<span class="label">ms</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="label">Channel</span>
<span class="value" id="status-channel">None</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="label">Online</span>
<span class="value" id="status-online-count">0</span>
</div>
<span class="spacer"></span>
<div class="status-item">
<span class="value" id="status-clock"></span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="value" id="status-version"></span>
</div>
</div>
</div>
<!-- Hidden audio container for voice streams -->
<div id="audio-container" style="display:none"></div>
<!-- Toast notifications -->
<div id="toast-container"></div>
<!-- Add Server Modal -->
<div class="modal-overlay" id="add-server-modal" style="display:none">
<div class="modal">
<h3>Add a Server</h3>
<p class="modal-desc">Connect to a friend's Haven server</p>
<div class="form-group">
<label>Server Name</label>
<input type="text" id="server-name-input" placeholder='e.g. Jake&apos;s Haven' maxlength="30">
</div>
<div class="form-group">
<label>Server Address</label>
<input type="text" id="server-url-input" placeholder="e.g. https://192.168.1.5:3000">
</div>
<div class="modal-actions">
<button class="btn-sm" id="cancel-server-btn">Cancel</button>
<button class="btn-sm btn-accent" id="save-server-btn">Add Server</button>
</div>
</div>
</div>
<!-- Rename / Profile Modal -->
<div class="modal-overlay" id="rename-modal" style="display:none">
<div class="modal">
<h3>Edit Profile</h3>
<!-- Avatar Section -->
<div class="avatar-settings" style="margin-bottom: 16px;">
<div class="avatar-preview-row">
<div class="avatar-preview-circle" id="rename-avatar-preview"></div>
<div class="avatar-actions">
<button type="button" class="btn-sm btn-accent" id="rename-avatar-upload-btn">Upload Image</button>
<button class="btn-sm" id="rename-avatar-remove-btn">Remove</button>
<input type="file" id="rename-avatar-file-input" accept="image/*" style="display:none">
</div>
</div>
<small class="settings-hint">Max 2 MB. Square images work best.</small>
</div>
<!-- Display Name -->
<p class="modal-desc">Letters, numbers, underscores, and spaces (2–20 chars)</p>
<div class="form-group">
<label>Display Name</label>
<input type="text" id="rename-input" placeholder="New display name..." maxlength="20">
</div>
<div class="modal-actions">
<button class="btn-sm" id="cancel-rename-btn">Cancel</button>
<button class="btn-sm btn-accent" id="save-rename-btn">Save</button>
</div>
</div>
</div>
<!-- Admin Action Modal (Kick/Ban/Mute) -->
<div class="modal-overlay" id="admin-action-modal" style="display:none">
<div class="modal">
<h3 id="admin-action-title">Moderate User</h3>
<p class="modal-desc" id="admin-action-desc"></p>
<div class="form-group" id="admin-reason-group">
<label>Reason (optional)</label>
<input type="text" id="admin-action-reason" placeholder="Reason..." maxlength="200">
</div>
<div class="form-group" id="admin-duration-group" style="display:none">
<label>Duration (minutes)</label>
<input type="number" id="admin-action-duration" value="10" min="1" max="43200" step="1">
</div>
<div class="modal-actions">
<button class="btn-sm" id="cancel-admin-action-btn">Cancel</button>
<button class="btn-sm btn-accent btn-danger-fill" id="confirm-admin-action-btn">Confirm</button>
</div>
</div>
</div>
<!-- Settings Popout Modal -->
<div class="modal-overlay" id="settings-modal" style="display:none">
<div class="modal modal-settings">
<div class="settings-header">
<h3>⚙️ Settings</h3>
<button class="settings-close-btn" id="close-settings-btn">&times;</button>
</div>
<!-- Avatar Section -->
<div class="settings-section">
<h5 class="settings-section-title">🖼️ Avatar</h5>
<div class="avatar-settings">
<div class="avatar-preview-row">
<div class="avatar-preview-circle" id="avatar-preview"></div>
<div class="avatar-actions">
<button type="button" class="btn-sm btn-accent" id="avatar-upload-btn">Upload Image</button>
<button class="btn-sm" id="avatar-remove-btn">Remove</button>
<input type="file" id="avatar-file-input" accept="image/*" style="display:none">
</div>
</div>
<small class="settings-hint">Max 2 MB. Square images work best.</small>
</div>
</div>
<!-- Sounds Section -->
<div class="settings-section">
<h5 class="settings-section-title">🔔 Sounds</h5>
<div class="notif-settings">
<label class="toggle-row">
<span>Notifications</span>
<input type="checkbox" id="notif-enabled" checked>
</label>
<label class="volume-row">
<span>Volume</span>
<input type="range" id="notif-volume" min="0" max="100" value="50" class="slider-sm">
</label>
<label class="select-row">
<span>Messages</span>
<select id="notif-msg-sound">
<option value="ping">Ping</option>
<option value="chime">Chime</option>
<option value="blip">Blip</option>
<option value="bell">Bell</option>
<option value="drop">Drop</option>
<option value="alert">Alert</option>
<option value="chord">Chord</option>
<option value="none">None</option>
</select>
</label>
<div class="notif-divider"></div>
<label class="volume-row">
<span>@Mention Vol</span>
<input type="range" id="notif-mention-volume" min="0" max="100" value="80" class="slider-sm">
</label>
<label class="select-row">
<span>@Mentions</span>
<select id="notif-mention-sound">
<option value="bell">Bell</option>
<option value="alert">Alert</option>
<option value="chord">Chord</option>
<option value="ping">Ping</option>
<option value="chime">Chime</option>
<option value="blip">Blip</option>
<option value="drop">Drop</option>
<option value="none">None</option>
</select>
</label>
<div class="notif-divider"></div>
<label class="select-row">
<span>User Joined</span>
<select id="notif-join-sound">
<option value="chime">Chime</option>
<option value="aim_door_open">AIM Door Open</option>
<option value="bell">Bell</option>
<option value="ping">Ping</option>
<option value="blip">Blip</option>
<option value="chord">Chord</option>
<option value="none">None</option>
</select>
</label>
<label class="select-row">
<span>User Left</span>
<select id="notif-leave-sound">
<option value="drop">Drop</option>
<option value="aim_door_close">AIM Door Close</option>
<option value="chime">Chime</option>
<option value="ping">Ping</option>
<option value="blip">Blip</option>
<option value="none">None</option>
</select>
</label>
</div>
</div>
<!-- Password Change Section -->
<div class="settings-section">
<h5 class="settings-section-title">🔒 Password</h5>
<div class="password-change-form">
<div class="form-group compact">
<input type="password" id="current-password" placeholder="Current password" maxlength="128" autocomplete="current-password">
</div>
<div class="form-group compact">
<input type="password" id="new-password" placeholder="New password (8+ chars)" maxlength="128" autocomplete="new-password">
</div>
<div class="form-group compact">
<input type="password" id="confirm-password" placeholder="Confirm new password" maxlength="128" autocomplete="new-password">
</div>
<button class="btn-sm btn-full" id="change-password-btn">Change Password</button>
<small class="settings-hint" id="password-status"></small>
</div>
</div>
<!-- Admin Section (hidden for non-admins) -->
<div class="settings-section admin-settings-section" id="admin-mod-panel" style="display:none">
<h5 class="settings-section-title">🛡️ Admin</h5>
<div class="admin-settings">
<label class="select-row">
<span>Show Members</span>
<select id="member-visibility-select">
<option value="online">Online Only</option>
<option value="all">All Members</option>
<option value="none">Hidden</option>
</select>
</label>
<button class="btn-sm btn-full" id="view-bans-btn">📋 View Bans</button>
</div>
<div class="admin-settings" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">� Whitelist</h5>
<label class="toggle-row">
<span>Enabled</span>
<input type="checkbox" id="whitelist-enabled">
</label>
<small class="settings-hint">When enabled, only pre-approved usernames can register</small>
<div id="whitelist-panel" style="margin-top:8px;">
<div class="whitelist-add-row">
<input type="text" id="whitelist-username-input" placeholder="Username..." maxlength="20" class="settings-text-input">
<button class="btn-sm btn-accent" id="whitelist-add-btn">Add</button>
</div>
<div id="whitelist-list" class="whitelist-list">
<p class="muted-text">Loading...</p>
</div>
</div>
</div>
<div class="admin-settings" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">�🗑️ Auto-Cleanup</h5>
<label class="toggle-row">
<span>Enabled</span>
<input type="checkbox" id="cleanup-enabled">
</label>
<label class="select-row">
<span>Max Age (days)</span>
<input type="number" id="cleanup-max-age" min="0" max="3650" value="0" class="settings-number-input">
</label>
<small class="settings-hint">Delete messages older than N days (0 = off)</small>
<label class="select-row">
<span>Max DB Size (MB)</span>
<input type="number" id="cleanup-max-size" min="0" max="100000" value="0" class="settings-number-input">
</label>
<small class="settings-hint">Trim oldest messages when DB exceeds N MB (0 = off)</small>
<button class="btn-sm btn-full" id="run-cleanup-now-btn" style="margin-top:4px;">🧹 Run Cleanup Now</button>
</div>
<div class="admin-settings" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🔊 Custom Sounds</h5>
<small class="settings-hint">Upload audio files for custom notification sounds (max 1 MB each)</small>
<div style="margin-top:8px;">
<div class="whitelist-add-row">
<input type="text" id="sound-name-input" placeholder="Sound name..." maxlength="30" class="settings-text-input">
<input type="file" id="sound-file-input" accept="audio/*" style="max-width:120px;font-size:11px">
<button class="btn-sm btn-accent" id="sound-upload-btn">Upload</button>
</div>
<div id="custom-sounds-list" style="margin-top:8px;">
<p class="muted-text">No custom sounds uploaded</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bans List Modal -->
<div class="modal-overlay" id="bans-modal" style="display:none">
<div class="modal modal-wide">
<h3>Banned Users</h3>
<div id="bans-list" class="bans-list">
<p class="muted-text">Loading...</p>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-bans-btn">Close</button>
</div>
</div>
</div>
<!-- Music Share Modal -->
<div class="modal-overlay" id="music-modal" style="display:none">
<div class="modal">
<h3>🎵 Listen Together</h3>
<p class="modal-desc">Paste a link from Spotify, YouTube, or SoundCloud to share with everyone in voice</p>
<div class="form-group">
<label>Music Link</label>
<input type="text" id="music-link-input" placeholder="https://open.spotify.com/track/..." maxlength="500">
</div>
<div class="music-link-preview" id="music-link-preview"></div>
<div class="music-platforms-hint">
<span class="platform-badge spotify">Spotify</span>
<span class="platform-badge youtube">YouTube</span>
<span class="platform-badge soundcloud">SoundCloud</span>
</div>
<div class="modal-actions">
<button class="btn-sm" id="cancel-music-btn">Cancel</button>
<button class="btn-sm btn-accent" id="share-music-btn">Share with Channel</button>
</div>
</div>
</div>
<!-- Channel context menu (appears on "..." hover button) -->
<div id="channel-ctx-menu" class="channel-ctx-menu" style="display:none">
<button class="channel-ctx-item" data-action="mute">🔔 Mute Channel</button>
<hr class="channel-ctx-sep">
<button class="channel-ctx-item admin-only" data-action="toggle-streams">🖥️ Toggle Streams</button>
<button class="channel-ctx-item admin-only" data-action="toggle-music">🎵 Toggle Music</button>
<hr class="channel-ctx-sep admin-only">
<button class="channel-ctx-item admin-only danger" data-action="delete">🗑️ Delete Channel</button>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="/js/theme.js"></script>
<script src="/js/notifications.js"></script>
<script src="/js/servers.js"></script>
<script src="/js/voice.js"></script>
<script src="/js/app.js"></script>
</body>
</html>