Haven/public/app.html

2858 lines
197 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, interactive-widget=resizes-content">
<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">
<link rel="manifest" href="/manifest.webmanifest">
<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?v=3.4.0">
<script src="/js/theme-init.js?v=3.4.0"></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" data-i18n-title="app.sidebar.this_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" data-i18n-title="app.sidebar.add_server" title="Add Server">
<span class="server-icon-text">+</span>
</button>
<button class="server-icon manage-servers" id="manage-servers-btn" data-i18n-title="app.sidebar.manage_servers" title="Manage Servers">
<span class="server-icon-text"></span>
</button>
<button class="server-icon sync-servers" id="sync-servers-btn" title="Sync Server List">
<span class="server-icon-text"></span>
</button>
</nav>
<!-- ─── Left Sidebar ────────────────────────────── -->
<aside class="sidebar">
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
<div class="sidebar-header">
<div class="brand">
<span class="logo-sm"></span>
<span class="brand-text">HAVEN</span>
</div>
<button id="mobile-sidebar-close" class="mobile-panel-close" data-i18n-title="modals.common.close" data-i18n-aria-label="app.actions.close_sidebar" title="Close" aria-label="Close sidebar">&times;</button>
<div class="user-bar">
<span class="led on" id="connection-led" data-i18n-title="app.status.server_connection" title="Server connection"></span>
<div class="user-names">
<span class="current-user" id="current-user"></span>
<span class="login-name" id="login-name" data-i18n-title="app.profile.login_username" title="Your login username"></span>
</div>
<button id="rename-btn" class="icon-btn small" data-i18n-title="app.actions.change_display_name" title="Change display name">✏️</button>
<button id="logout-btn" class="icon-btn" data-i18n-title="app.actions.logout" title="Logout">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
</button>
</div>
</div>
<div class="sidebar-mod-container" id="sidebar-mod-container">
<!-- Mobile server bubbles (visible only on mobile) -->
<div class="sidebar-section mobile-servers-section" id="mobile-servers-section">
<h5 class="section-label mobile-servers-toggle" id="mobile-servers-toggle" style="cursor:pointer;user-select:none">
<span class="collapse-toggle-arrow" id="mobile-servers-arrow"></span>
<span class="section-label-text" style="flex:1" data-i18n="app.sidebar.servers">Servers</span>
</h5>
<div class="mobile-servers-row" id="mobile-servers-row">
<div class="mobile-servers-scroll" id="mobile-servers-scroll"></div>
<button class="mobile-srv-add-btn" id="mobile-srv-add-btn" data-i18n-title="app.sidebar.add_server" title="Add Server">+</button>
</div>
</div>
<div class="sidebar-section" data-mod-id="join">
<h5 class="section-label collapsible-section-toggle" id="join-section-toggle" style="cursor:pointer;user-select:none">
<span class="collapse-toggle-arrow" id="join-section-arrow"></span>
<span class="section-label-text" style="flex:1" data-i18n="app.sidebar.join_channel">Join a Channel</span>
</h5>
<div class="collapsible-section-body" id="join-section-body">
<div class="input-row">
<input type="text" id="channel-code-input"
data-i18n-placeholder="app.sidebar.join_placeholder" placeholder="Enter code..." maxlength="8" spellcheck="false">
<button id="join-channel-btn" class="btn-sm" data-i18n="app.sidebar.join_btn">Join</button>
</div>
</div>
</div>
<div class="sidebar-section" id="admin-controls" data-mod-id="create" style="display:none">
<h5 class="section-label collapsible-section-toggle" id="create-section-toggle" style="cursor:pointer;user-select:none">
<span class="collapse-toggle-arrow" id="create-section-arrow"></span>
<span class="section-label-text" style="flex:1" data-i18n="app.sidebar.create_channel">Create Channel</span>
</h5>
<div class="collapsible-section-body" id="create-section-body">
<div class="input-row">
<input type="text" id="new-channel-name"
data-i18n-placeholder="app.sidebar.channel_name_placeholder" placeholder="Channel name..." maxlength="50">
<button id="create-channel-btn" class="btn-sm btn-accent">+</button>
</div>
<label class="checkbox-row" style="margin:6px 0 2px;display:flex;align-items:center;gap:8px;cursor:pointer;font-size:0.8rem;opacity:0.8">
<input type="checkbox" id="new-channel-private" style="width:14px;height:14px">
<span>🔒 <span data-i18n="app.sidebar.private_channel">Private</span></span> <span style="opacity:0.5;font-size:0.75rem" data-i18n="app.sidebar.private_hint">(invite-only)</span>
</label>
<label class="checkbox-row" style="margin:2px 0;display:flex;align-items:center;gap:8px;cursor:pointer;font-size:0.8rem;opacity:0.8">
<input type="checkbox" id="new-channel-temporary" style="width:14px;height:14px">
<span>⏱️ <span data-i18n="app.sidebar.temporary_channel">Temporary</span></span> <span style="opacity:0.5;font-size:0.75rem" data-i18n="app.sidebar.temporary_hint">(auto-delete)</span>
</label>
<div id="temp-channel-duration-row" style="display:none;margin:4px 0 2px;padding-left:22px">
<div class="input-row" style="gap:6px;align-items:center">
<input type="number" id="new-channel-duration" min="1" max="720" value="24" style="width:60px;font-size:0.8rem" placeholder="Hours">
<span style="font-size:0.75rem;opacity:0.6" data-i18n="app.sidebar.hours">hours</span>
</div>
</div>
</div>
</div>
<div class="sidebar-split" id="sidebar-split" data-mod-id="channels">
<div class="sidebar-section channel-section" id="channels-pane">
<h5 class="section-label channels-toggle" id="channels-toggle" style="cursor:pointer;user-select:none"><span class="channels-toggle-arrow" id="channels-toggle-arrow"></span><span class="section-label-text" style="flex:1" data-i18n="app.sidebar.channels">Channels</span><button class="icon-btn small" id="sub-channel-panel-btn" data-i18n-title="app.actions.sub_channel_subscriptions" title="Sub-channel subscriptions" style="font-size:0.85rem;padding:2px 6px;opacity:0.6;position:relative;z-index:2;display:none">📡</button><button class="icon-btn small" id="organize-channels-btn" data-i18n-title="app.actions.organize_channels" title="Organize channels" style="display:none;font-size:0.85rem;padding:2px 6px;opacity:0.6;position:relative;z-index:2">📋</button></h5>
<div class="channel-list" id="channel-list"></div>
</div>
<div class="sidebar-split-handle" id="sidebar-split-handle" data-i18n-title="app.actions.drag_to_resize" title="Drag to resize"></div>
<div class="sidebar-section dm-section-pane" id="dm-pane">
<h5 class="section-label dm-section-label dm-toggle" id="dm-toggle-header" style="cursor:pointer;user-select:none"><span class="dm-toggle-arrow" id="dm-toggle-arrow"></span><span class="section-label-text" style="flex:1" data-i18n="app.sidebar.direct_messages">Direct Messages</span><span style="display:flex;align-items:center;gap:4px;position:relative;z-index:2"><span class="dm-unread-count" id="dm-unread-badge" style="display:none"></span><button class="icon-btn small" id="organize-dms-btn" data-i18n-title="app.actions.organize_dms" title="Organize DMs" style="font-size:0.85rem;padding:2px 6px;opacity:0.6">📋</button></span></h5>
<div class="dm-list-scroll" id="dm-list"></div>
</div>
</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" data-i18n="app.theme.title">Theme</span>
<button id="theme-popup-close" class="icon-btn small" data-i18n-title="app.theme.close" 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="scripture" title="Scripture"><span class="theme-icon">✝️</span></button>
<button class="theme-btn" data-theme="chapel" title="Chapel"><span class="theme-icon"></span></button>
<button class="theme-btn" data-theme="gospel" title="Gospel"><span class="theme-icon">🕊️</span></button>
<button class="theme-btn" data-theme="midnightpurple" title="Midnight Purple"><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>
<button class="theme-btn" data-theme="daylight" title="Daylight (Light)"><span class="theme-icon">☀️</span></button>
<button class="theme-btn" data-theme="cloudy" title="Cloudy (Light)"><span class="theme-icon">☁️</span></button>
</div>
<!-- Effect overlay picker (mashup) -->
<div class="effect-overlay-section">
<div class="theme-popup-title" style="margin-top: 2px;" data-i18n="app.theme.effect_overlay">Effect Overlay</div>
<div class="effect-selector" id="effect-selector">
<button class="effect-btn active" data-effect="auto" title="Match theme"></button>
<button class="effect-btn" data-effect="none" title="No effects">🚫</button>
<button class="effect-btn" data-effect="crt" title="CRT Scanlines">📺</button>
<button class="effect-btn" data-effect="matrix" title="Matrix Rain"></button>
<button class="effect-btn" data-effect="matrixbars" title="Matrix Scan Lines"></button>
<button class="effect-btn" data-effect="nord" title="Snowfall"></button>
<button class="effect-btn" data-effect="darksouls" title="Campfire">🔥</button>
<button class="effect-btn" data-effect="eldenring" title="Golden Grace">💍</button>
<button class="effect-btn" data-effect="bloodborne" title="Blood Vignette">🩸</button>
<button class="effect-btn" data-effect="fallout" title="Phosphor Glow">☢️</button>
<button class="effect-btn" data-effect="ffx" title="Water Flow">⚔️</button>
<button class="effect-btn" data-effect="ice" title="Frost Shimmer">🧊</button>
<button class="effect-btn" data-effect="cyberpunk" title="Glitch"></button>
<button class="effect-btn" data-effect="lotr" title="Candlelight"></button>
<button class="effect-btn" data-effect="abyss" title="Ocean Depth">🌊</button>
<button class="effect-btn" data-effect="scripture" title="Cross Light">✝️</button>
<button class="effect-btn" data-effect="chapel" title="Stained Glass"></button>
<button class="effect-btn" data-effect="gospel" title="Divine Radiance">🕊️</button>
</div>
</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" data-i18n="app.theme.speed">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" data-i18n="app.theme.vibrancy">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" data-i18n="app.theme.effect_speed">Effect Speed</span>
<input type="range" id="effect-speed-slider" min="15" max="200" value="100" class="slider-sm rgb-slider">
</label>
</div>
<!-- Sacred intensity slider (religious themes) -->
<div id="sacred-intensity-editor" class="rgb-theme-editor" style="display:none">
<label class="rgb-slider-row">
<span class="rgb-slider-label">Sacred Intensity</span>
<input type="range" id="sacred-intensity-slider" min="20" max="250" value="100" class="slider-sm rgb-slider">
</label>
</div>
<!-- Glitch frequency slider (cyberpunk) -->
<div id="glitch-freq-editor" class="rgb-theme-editor" style="display:none">
<label class="rgb-slider-row">
<span class="rgb-slider-label">Glitch Frequency</span>
<input type="range" id="glitch-freq-slider" min="5" max="100" value="50" class="slider-sm rgb-slider">
</label>
</div>
<!-- CRT effect sliders (vignette darkness + scanline intensity) -->
<div id="crt-editor" class="rgb-theme-editor" style="display:none">
<label class="rgb-slider-row">
<span class="rgb-slider-label">CRT Vignette</span>
<input type="range" id="crt-vignette-slider" min="0" max="100" value="50" class="slider-sm rgb-slider">
</label>
<label class="rgb-slider-row">
<span class="rgb-slider-label">CRT Scanlines</span>
<input type="range" id="crt-scanline-slider" min="0" max="80" value="45" class="slider-sm rgb-slider">
</label>
</div>
</div>
<!-- Bottom bar -->
<div id="voice-bar" class="voice-bar" style="display:none"></div>
<div class="sidebar-bottom-bar">
<button id="theme-popup-toggle" class="sidebar-bottom-btn" data-i18n-title="app.theme.title" title="Themes">🎨</button>
<button class="sidebar-bottom-btn" id="activities-btn" data-i18n-title="modals.activities.title" title="Activities">🎮</button>
<button class="sidebar-bottom-btn" id="sidebar-members-btn" data-i18n-title="modals.all_members.title" title="All Members" style="display:none">👥</button>
<button id="mobile-settings-btn" class="sidebar-bottom-btn mobile-settings-btn" data-i18n-title="settings.title" title="Settings">⚙️</button>
<span style="flex:1"></span>
<button id="donors-btn" class="sidebar-bottom-btn donate-btn">❤️</button>
<button id="voice-mute-btn" class="sidebar-bottom-btn voice-header-btn" title="Mute" style="display:none">🎙️</button>
<button id="voice-deafen-btn" class="sidebar-bottom-btn voice-header-btn" title="Deafen" style="display:none">🔊</button>
</div>
</div>
</aside>
<!-- ─── Main Content ────────────────────────────── -->
<main class="main">
<!-- Server Banner (always behind all content) -->
<div id="server-banner-display" class="server-banner-display" style="display:none">
<img id="server-banner-img" class="server-banner-img" src="" alt="Server banner">
</div>
<header class="channel-header">
<button id="mobile-menu-btn" class="mobile-menu-btn" data-i18n-title="header.mobile_menu" data-i18n-aria-label="header.mobile_menu" 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="mobile-server-dropdown" id="mobile-server-dropdown">
<button class="mobile-server-btn" id="mobile-server-btn" data-i18n-title="header.mobile_servers" data-i18n-aria-label="header.mobile_servers" title="Servers" aria-label="Servers"></button>
<div class="mobile-server-menu" id="mobile-server-menu">
<div class="mobile-server-menu-header" data-i18n="header.mobile_servers">Servers</div>
<div id="mobile-server-list"></div>
<button class="mobile-server-add" id="mobile-server-add-btn" data-i18n="header.mobile_add_server">+ Add Server</button>
</div>
</div>
<div class="channel-info">
<h3 id="channel-header-name" data-i18n="header.select_channel">Select a channel</h3>
</div>
<div class="header-actions-box" id="header-actions-box" style="display:none">
<span class="channel-code-tag" id="channel-code-display" data-i18n-title="header.channel_code" title="Channel code"></span>
<button id="copy-code-btn" class="icon-btn small" data-i18n-title="header.copy_code" title="Copy code">📋</button>
<button id="channel-code-settings-btn" class="icon-btn small" data-i18n-title="header.channel_code_settings" title="Channel code settings">⚙️</button>
<span class="header-actions-divider"></span>
<button id="search-toggle-btn" class="icon-btn small" data-i18n-title="header.search_messages" title="Search messages (Ctrl+F)">🔍</button>
<button id="pinned-toggle-btn" class="icon-btn small" data-i18n-title="header.pinned_messages" title="Pinned messages">📌</button>
<button id="move-select-btn" class="icon-btn small" data-i18n-title="header.select_messages_to_move" title="Select messages to move" style="display:none"></button>
<div class="e2e-menu-wrapper" style="display:none" id="e2e-menu-wrapper">
<button id="e2e-menu-btn" class="icon-btn small" data-i18n-title="header.encryption_options" title="Encryption options">🔐</button>
<div class="e2e-dropdown" id="e2e-dropdown" style="display:none">
<button class="e2e-dropdown-item" id="e2e-verify-btn">🔑 <span data-i18n="header.verify_encryption">Verify Encryption</span></button>
<button class="e2e-dropdown-item e2e-danger" id="e2e-reset-btn">🔄 <span data-i18n="header.reset_encryption">Reset Encryption Keys</span></button>
</div>
</div>
</div>
<div class="search-container" id="search-container" style="display:none">
<input type="text" id="search-input" class="search-input" data-i18n-placeholder="header.search_placeholder" placeholder="Search... from:user in:#channel has:image" maxlength="200">
<button id="search-close-btn" class="icon-btn small" title="Close search">&times;</button>
</div>
<!-- Update available banner -->
<a id="update-banner" class="update-banner" href="#" style="display:none" target="_blank" rel="noopener">
<span class="update-pulse"></span>
<span class="update-text">Update Available</span>
</a>
<!-- Desktop app banner -->
<a id="desktop-app-banner" class="desktop-app-banner" href="https://ancsemi.github.io/Haven/#download" target="_blank" rel="noopener" style="display:none">
<span class="desktop-app-icon">🖥️</span>
<span class="desktop-app-text" data-i18n="header.get_desktop_app">Get the Desktop App</span>
<button class="desktop-app-dismiss" id="desktop-app-dismiss" title="Dismiss">&times;</button>
</a>
<!-- Android beta banner (must be <div>, not <button>; nesting
<button> inside <button> is invalid HTML and causes the
dismiss × to be hoisted out of its parent by the parser,
creating a phantom X in the channel header on mobile) -->
<div id="android-beta-banner" class="android-beta-banner" role="button" tabindex="0" style="display:none">
<span class="android-beta-icon">📱</span>
<span class="android-beta-text" data-i18n="header.android_beta">Android App</span>
<button class="android-beta-dismiss" id="android-beta-dismiss" title="Dismiss">&times;</button>
</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"><span data-i18n="voice.join">🎤 Join Voice</span></button>
<div id="voice-active-indicator" class="voice-active-indicator" style="display:none">
<span class="voice-indicator-icon">🎤</span>
<span class="voice-indicator-text" data-i18n="header.voice_active">Voice Active</span>
</div>
</div>
</header>
<!-- Welcome Screen -->
<div id="no-channel-msg" class="welcome-screen">
<div class="welcome-content">
<div class="welcome-icon"></div>
<h2 data-i18n="welcome.title">Welcome to Haven</h2>
<p data-i18n="welcome.subtitle">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">
<!-- Webcam Grid -->
<div id="webcam-container" class="webcam-container" style="display:none">
<div class="webcam-header">
<span id="webcam-label">📷 <span data-i18n="media.cameras">Cameras</span></span>
<div class="webcam-header-controls">
<div class="webcam-layout-picker" id="webcam-layout-picker" data-i18n-title="media.camera_layout" title="Camera layout">
<button class="stream-layout-btn" id="webcam-layout-btn" data-i18n-title="media.change_layout" title="Change layout"></button>
<div class="stream-layout-menu" id="webcam-layout-menu">
<button class="stream-layout-opt active" data-layout="auto" data-i18n-title="media.layout_auto_title" title="Auto grid">⊞ Auto</button>
<button class="stream-layout-opt" data-layout="vertical" data-i18n-title="media.layout_stack_title" title="Vertical stack">▬ Stack</button>
<button class="stream-layout-opt" data-layout="side-by-side" data-i18n-title="media.layout_side_title" title="Side by side">◫ Side</button>
<button class="stream-layout-opt" data-layout="grid-2x2" data-i18n-title="media.layout_2x2_title" title="2×2 Grid">⊟ 2×2</button>
</div>
</div>
<label class="stream-size-label" data-i18n-title="media.camera_size" title="Camera display size">
📷
<input type="range" id="webcam-size-slider" class="stream-size-slider" min="15" max="70" value="25">
</label>
<button id="webcam-collapse-btn" class="icon-btn small" data-i18n-title="media.minimize_cameras" title="Minimize cameras"></button>
<button id="webcam-close-btn" class="icon-btn small" data-i18n-title="media.close_cameras" title="Close cameras"></button>
</div>
</div>
<div id="webcam-grid" class="webcam-grid"></div>
</div>
<!-- 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">🖥️ <span data-i18n="media.streams">Streams</span></span>
<div class="screen-share-header-controls">
<div class="stream-layout-picker" id="stream-layout-picker" data-i18n-title="media.stream_layout" title="Stream layout">
<button class="stream-layout-btn" id="stream-layout-btn" data-i18n-title="media.change_layout" title="Change layout"></button>
<div class="stream-layout-menu" id="stream-layout-menu">
<button class="stream-layout-opt active" data-layout="auto" data-i18n-title="media.layout_auto_title" title="Auto grid">⊞ Auto</button>
<button class="stream-layout-opt" data-layout="vertical" data-i18n-title="media.layout_stack_title" title="Vertical stack">▬ Stack</button>
<button class="stream-layout-opt" data-layout="side-by-side" data-i18n-title="media.layout_side_title" title="Side by side">◫ Side</button>
<button class="stream-layout-opt" data-layout="grid-2x2" data-i18n-title="media.layout_2x2_title" title="2×2 Grid">⊟ 2×2</button>
</div>
</div>
<label class="stream-size-label" data-i18n-title="media.stream_size" 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" data-i18n-title="media.minimize_streams" title="Minimize streams"></button>
<button id="screen-share-close" class="icon-btn small" data-i18n-title="media.close_streams" 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" data-i18n-title="media.music_popout" title="Pop out player"></button>
<div class="music-panel-copy">
<span id="music-panel-label">🎵 <span data-i18n="media.listening_together">Listening Together</span></span>
<span id="music-up-next" class="music-up-next" data-i18n="media.music_up_next_empty">Up next: Nothing queued</span>
</div>
</div>
<span id="music-activity-hint" class="music-activity-hint"></span>
<div class="music-panel-controls">
<button id="music-queue-btn" class="icon-btn small" data-i18n-title="media.music_queue" title="Queue"></button>
<button id="music-play-pause-btn" class="icon-btn small" data-i18n-title="media.music_play_pause" title="Play/Pause"></button>
<button id="music-next-btn" class="icon-btn small" data-i18n-title="media.music_next" title="Next track" style="display:none"></button>
<span id="music-time-current" class="music-time">0:00</span>
<input type="range" id="music-seek-slider" class="music-seek-slider" min="0" max="100" value="0" step="0.1" data-i18n-title="media.music_seek" title="Seek">
<span id="music-time-duration" class="music-time">0:00</span>
<button id="music-mute-btn" class="icon-btn small" data-i18n-title="media.music_mute" title="Mute music">🔊</button>
<input type="range" id="music-volume-slider" class="stream-vol-slider" min="0" max="100" value="80" data-i18n-title="media.music_volume" title="Music volume">
<button id="music-close-btn" class="icon-btn small" data-i18n-title="media.minimize" title="Minimize"></button>
<button id="music-stop-btn" class="icon-btn small" data-i18n-title="media.music_stop" 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" data-i18n="header.results">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">📌 <span data-i18n="header.pinned_count">Pinned Messages</span></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">
<!-- Floating "⋯" button for touch-device message actions fallback -->
<button id="msg-more-btn" aria-label="Message actions"></button>
</div>
<button id="jump-to-bottom" class="jump-to-bottom" data-i18n-title="app.actions.jump_to_bottom" title="Jump to bottom">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<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 id="image-queue-bar" class="image-queue-bar" style="display:none"></div>
<div id="upload-progress-bar" class="upload-progress-bar" style="display:none">
<div class="upload-progress-track"><div class="upload-progress-fill" id="upload-progress-fill"></div></div>
<span class="upload-progress-text" id="upload-progress-text" data-i18n="header.uploading">Uploading...</span>
</div>
<div id="message-input-area" class="message-input-area">
<div class="input-actions-box">
<button id="upload-btn" class="btn-upload" data-i18n-title="media.upload_file_title" title="Upload File">
<svg class="upload-icon-default" 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>
<span class="upload-icon-clippy">📎</span>
</button>
<span class="input-actions-divider"></span>
<button id="emoji-btn" class="btn-emoji" data-i18n-title="app.input_bar.emoji_btn" title="Emoji">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
<circle cx="9" cy="10" r="1.2" fill="currentColor" stroke="none"/>
<circle cx="15" cy="10" r="1.2" fill="currentColor" stroke="none"/>
</svg>
</button>
<span class="input-actions-divider"></span>
<button id="gif-btn" class="btn-gif" data-i18n-title="app.input_bar.gif_btn" title="Search GIFs"><span data-i18n="app.input_bar.gif_label">GIF</span></button>
<span class="input-actions-divider"></span>
<button id="poll-btn" class="btn-poll" data-i18n-title="app.input_bar.poll_btn" title="Create Poll">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="6" y1="20" x2="6" y2="14"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="2" y1="20" x2="22" y2="20"/>
</svg>
</button>
</div>
<div id="emoji-picker" class="emoji-picker" style="display:none"></div>
<div id="gif-picker" class="gif-picker" style="display:none">
<div class="gif-picker-header">
<input type="text" id="gif-search-input" data-i18n-placeholder="header.gif_search_placeholder" placeholder="Search GIPHY..." maxlength="100" autocomplete="off">
</div>
<div class="gif-picker-grid" id="gif-grid"></div>
<div class="gif-picker-footer" data-i18n="header.powered_by_giphy">Powered by GIPHY</div>
</div>
<div id="mention-dropdown" class="mention-dropdown" style="display:none"></div>
<div id="emoji-dropdown" class="emoji-dropdown" style="display:none"></div>
<div id="slash-dropdown" class="slash-dropdown" style="display:none"></div>
<textarea id="message-input" data-i18n-placeholder="header.message_placeholder_commands" placeholder="Type a message... (/ for commands)"
rows="1" maxlength="2000"></textarea>
<button id="send-btn" class="btn-send" data-i18n-title="media.send_title" 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" style="display:none">
</div>
</main>
<!-- Sidebar collapse arrow — fixed to the right edge of the viewport -->
<button id="sidebar-toggle-btn" class="sidebar-collapse-btn" data-i18n-title="app.actions.toggle_members_panel" data-i18n-aria-label="app.actions.toggle_members_panel" title="Toggle members panel" aria-label="Toggle members panel">&#x276F;</button>
<!-- ─── Right Sidebar ───────────────────────────── -->
<aside class="right-sidebar" id="right-sidebar">
<div class="right-sidebar-resize-handle" id="right-sidebar-resize-handle"></div>
<!-- Voice users section -->
<div class="right-sidebar-voice" id="right-sidebar-voice">
<div class="panel">
<div class="voice-panel-header">
<h5 class="panel-title" style="margin-bottom:0">🔊 <span data-i18n="right_sidebar.voice_title">Voice</span></h5>
<button id="mobile-right-close" class="mobile-panel-close mobile-panel-close-right" data-i18n-title="modals.common.close" data-i18n-aria-label="right_sidebar.close_panel" title="Close" aria-label="Close panel">&times;</button>
<div class="voice-header-actions">
<button id="voice-mute-btn-header" class="voice-header-btn" data-i18n-title="voice.mute" title="Mute" style="display:none">🎙️</button>
<button id="voice-deafen-btn-header" class="voice-header-btn" data-i18n-title="voice.deafen" title="Deafen" style="display:none">🔊</button>
</div>
</div>
<button id="voice-join-mobile" class="btn-voice mobile-voice-join" style="display:none">🎤 <span data-i18n="voice.join">Join Voice</span></button>
<div id="voice-users" class="user-list">
<p class="muted-text" data-i18n="right_sidebar.no_one_in_voice">No one in voice</p>
</div>
</div>
</div>
<!-- Scrollable users section -->
<div class="right-sidebar-users" id="right-sidebar-users">
<div class="panel">
<h5 class="panel-title">👥 <span data-i18n="right_sidebar.online_title">Online</span></h5>
<div id="online-users" class="user-list">
<p class="muted-text" data-i18n="right_sidebar.select_channel">Select a channel</p>
</div>
</div>
</div>
<!-- Voice settings sub-panel (slides up above controls) -->
<div id="voice-settings-panel" class="voice-settings-panel" style="display:none">
<div class="voice-settings-section-label">🎧 <span data-i18n="voice_settings.audio_devices">Audio Devices</span></div>
<div class="voice-settings-row">
<label class="voice-settings-label" data-i18n="voice_settings.input">Input</label>
<select id="voice-input-device" class="voice-settings-select">
<option value="" data-i18n="voice_settings.default_mic">Default Microphone</option>
</select>
</div>
<div class="voice-settings-row">
<label class="voice-settings-label" data-i18n="voice_settings.output">Output</label>
<select id="voice-output-device" class="voice-settings-select">
<option value="" data-i18n="voice_settings.default_speaker">Default Speaker</option>
</select>
</div>
<div class="voice-settings-row">
<label class="voice-settings-label" data-i18n="voice_settings.camera">Camera</label>
<select id="voice-cam-device" class="voice-settings-select">
<option value="" data-i18n="voice_settings.default_camera">Default Camera</option>
</select>
</div>
<div class="voice-settings-divider"></div>
<div class="voice-settings-row">
<label class="voice-settings-label">🤫 <span data-i18n="voice_settings.noise_reduction">Noise Reduction</span></label>
<select id="voice-noise-mode" class="voice-settings-select" data-i18n-title="voice_settings.noise_mode_hint" title="Off = no processing, Gate = silence below threshold, Suppress = AI noise removal (RNNoise)">
<option value="off" data-i18n="voice_settings.off">Off</option>
<option value="gate" data-i18n="voice_settings.noise_gate">Noise Gate</option>
<option value="suppress" data-i18n="voice_settings.suppression_ai">Suppression (AI)</option>
</select>
</div>
<div class="voice-settings-row" id="noise-gate-row">
<label class="voice-settings-label" style="font-size:0.72rem;opacity:0.8" data-i18n="voice_settings.gate_sensitivity">Gate Sensitivity</label>
<input type="range" id="voice-ns-slider" class="ns-slider" min="0" max="100" value="10" data-i18n-title="voice_settings.gate_slider_hint" title="Noise gate: 0 = off, 100 = aggressive">
</div>
<div class="voice-settings-row mic-meter-row">
<label class="voice-settings-label">🎙️ <span data-i18n="voice_settings.mic_level">Mic Level</span></label>
<div class="mic-meter" id="mic-level-meter">
<div class="mic-meter-fill" id="mic-meter-fill"></div>
<div class="mic-meter-threshold" id="mic-meter-threshold" data-i18n-title="voice_settings.gate_threshold" title="Gate threshold"></div>
</div>
</div>
<div class="voice-settings-divider"></div>
<div class="voice-settings-section-label">🖥️ <span data-i18n="voice_settings.screen_share_quality">Screen Share Quality</span></div>
<div class="voice-settings-row">
<label class="voice-settings-label" data-i18n="voice_settings.resolution">Resolution</label>
<select id="screen-res-select" class="voice-settings-select">
<option value="source" data-i18n="voice_settings.resolution_source">Source</option>
<option value="720">720p</option>
<option value="1080" selected>1080p</option>
<option value="1440">1440p</option>
</select>
</div>
<div class="voice-settings-row">
<label class="voice-settings-label" data-i18n="voice_settings.frame_rate">Frame Rate</label>
<select id="screen-fps-select" class="voice-settings-select">
<option value="15">15 fps</option>
<option value="30" selected>30 fps</option>
<option value="60">60 fps</option>
</select>
</div>
</div>
<!-- Voice controls bar (pinned above settings) -->
<div id="voice-panel" class="voice-panel" style="display:none">
<button id="voice-cam-btn" class="voice-panel-btn" data-i18n-title="voice.panel.camera" title="Camera">📷</button>
<button id="screen-share-btn" class="voice-panel-btn" data-i18n-title="voice.screen_share" title="Share Screen">🖥️</button>
<button id="voice-soundboard-btn" class="voice-panel-btn" data-i18n-title="voice.panel.soundboard" title="Soundboard">🎵</button>
<button id="voice-listen-together-btn" class="voice-panel-btn" data-i18n-title="voice.panel.listen_together" title="Listen Together">🎶</button>
<span class="voice-panel-divider"></span>
<button id="voice-settings-toggle" class="voice-panel-btn" data-i18n-title="voice.panel.settings" title="Voice Settings">⚙️</button>
<button id="voice-leave-sidebar-btn" class="voice-panel-btn voice-panel-leave" data-i18n-title="voice.leave" title="Leave Voice"></button>
</div>
<!-- Settings button pinned at bottom -->
<div class="panel sidebar-settings-panel">
<button class="btn-settings-popout" id="open-settings-btn" data-i18n-title="app.actions.settings" 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 data-i18n="app.actions.settings">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" style="display:none">
<div class="status-item">
<span class="led on" id="status-server-led"></span>
<span class="label" data-i18n="status_bar.server">Server</span>
<span class="value" id="status-server-text" data-i18n="app.status.connected">Connected</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="label" data-i18n="status_bar.ping">Ping</span>
<span class="value" id="status-ping">--</span>
<span class="label" data-i18n="status_bar.ping_unit">ms</span>
</div>
<div class="divider"></div>
<div class="status-item">
<span class="label" data-i18n="status_bar.channel">Channel</span>
<span class="value" id="status-channel" data-i18n="status_bar.channel_none">None</span>
</div>
<div class="divider"></div>
<div class="status-item status-online-trigger" id="status-online-trigger" title="Click to see who's online">
<span class="label" data-i18n="status_bar.online">Online</span>
<span class="value" id="status-online-count">0</span>
</div>
<span class="spacer"></span>
<div class="status-item status-url-item" id="status-url-item">
<span class="value status-url-text" id="status-url-text" title="Click to copy server address"></span>
<button class="status-url-toggle" id="status-url-toggle" title="Show/hide server address">👁</button>
</div>
<div class="divider"></div>
<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>
<!-- Status bar toggle tab (visible when bar is hidden) -->
<button class="status-bar-toggle-tab" id="status-bar-toggle" title="Toggle status bar (debug footer)">📊</button>
<!-- Online users overlay (floats above status bar) -->
<div id="online-overlay" class="online-overlay" style="display:none">
<div class="online-overlay-header">
<span class="online-overlay-title" data-i18n="status_bar.users_overlay_title">Users</span>
<button id="online-overlay-close" class="icon-btn small" title="Close">&times;</button>
</div>
<div id="online-overlay-list" class="online-overlay-list"></div>
</div>
</div>
<!-- Hidden audio container for voice streams -->
<div id="audio-container" style="display:none"></div>
<!-- Toast notifications -->
<div id="toast-container"></div>
<!-- Move Messages Toolbar (floating above messages) -->
<div id="move-msg-toolbar" class="move-msg-toolbar" style="display:none">
<span id="move-msg-count">0 selected</span>
<button class="btn-sm btn-accent" id="move-msg-move-btn" data-i18n="modals.move_messages.move_to">Move to...</button>
<button class="btn-sm" id="move-msg-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
</div>
<!-- Move Messages Channel Picker Modal -->
<div class="modal-overlay" id="move-msg-modal" style="display:none">
<div class="modal">
<h3 data-i18n="modals.move_messages.title">Move Messages</h3>
<p class="modal-desc" id="move-msg-desc" data-i18n="modals.move_messages.desc">Select a destination channel</p>
<div class="move-msg-channel-list" id="move-msg-channel-list"></div>
<div class="modal-actions">
<button class="btn-sm" id="move-msg-modal-cancel" data-i18n="modals.common.cancel">Cancel</button>
</div>
</div>
</div>
<!-- Thread Panel (slides in from right) -->
<div id="thread-panel" class="thread-panel" style="display:none">
<div class="thread-panel-resizer" id="thread-panel-resizer" aria-hidden="true"></div>
<div class="thread-panel-header">
<div class="thread-panel-header-top">
<span class="thread-panel-icon">🧵</span>
<span id="thread-panel-title" class="thread-panel-title">Thread</span>
<button id="thread-panel-pip" class="icon-btn small" title="Pop out thread (PiP)" aria-pressed="false"></button>
<button id="thread-panel-close" class="icon-btn small">&times;</button>
</div>
<div class="thread-parent-meta" id="thread-parent-meta">
<div class="thread-parent-avatar-wrap" id="thread-parent-avatar-wrap"></div>
<span class="thread-parent-name" id="thread-parent-name">Thread starter</span>
</div>
<div class="thread-parent-preview" id="thread-parent-preview"></div>
</div>
<div class="thread-messages" id="thread-messages"></div>
<div class="thread-reply-bar" id="thread-reply-bar" style="display:none">
<span id="thread-reply-preview-text"></span>
<button id="thread-reply-close-btn" class="icon-btn small" aria-label="Cancel thread reply">&times;</button>
</div>
<div class="thread-input-area">
<textarea id="thread-input" class="thread-input" placeholder="Reply in thread..." rows="1"></textarea>
<button id="thread-send-btn" class="thread-send-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/>
</svg>
</button>
</div>
</div>
<!-- First-Time Setup Wizard (Admin only) -->
<div class="modal-overlay" id="setup-wizard-modal" style="display:none">
<div class="modal modal-wizard">
<div class="wizard-header">
<span class="wizard-hex"></span>
<h2 data-i18n="modals.wizard.title">Welcome to Haven</h2>
<p class="wizard-subtitle" data-i18n="modals.wizard.subtitle">Let's get your server ready. This only takes a minute.</p>
</div>
<div class="wizard-steps">
<!-- Step indicators -->
<div class="wizard-step-indicators">
<div class="wizard-indicator active" data-step="1">1</div>
<div class="wizard-indicator-line"></div>
<div class="wizard-indicator" data-step="2">2</div>
<div class="wizard-indicator-line"></div>
<div class="wizard-indicator" data-step="3">3</div>
<div class="wizard-indicator-line"></div>
<div class="wizard-indicator" data-step="4">4</div>
</div>
<!-- Step 1: Server basics -->
<div class="wizard-step" id="wizard-step-1" style="display:block">
<h3>🏠 <span data-i18n="modals.wizard.step1_title">Your Server</span></h3>
<p data-i18n="modals.wizard.step1_desc">Everything is already running. Here's what was set up automatically:</p>
<div class="wizard-checklist">
<div class="wizard-check"><span data-i18n="modals.wizard.check_ssl">SSL certificates generated (encrypted connections)</span></div>
<div class="wizard-check"><span data-i18n="modals.wizard.check_db">Database created</span></div>
<div class="wizard-check"><span data-i18n="modals.wizard.check_keys">Security keys generated</span></div>
<div class="wizard-check"><span data-i18n="modals.wizard.check_admin">Admin account created (that's you!)</span></div>
</div>
<div class="wizard-info-box">
<strong data-i18n="modals.wizard.server_name_label">Your server name:</strong>
<input type="text" id="wizard-server-name" class="wizard-input" value="Haven" placeholder="e.g. My Server, The Hideout" data-i18n-placeholder="modals.wizard.server_name_placeholder">
<span class="wizard-hint" data-i18n="modals.wizard.server_name_hint">This is what your friends see in their server list.</span>
</div>
</div>
<!-- Step 2: Create first channel -->
<div class="wizard-step" id="wizard-step-2" style="display:none">
<h3>📢 <span data-i18n="modals.wizard.step2_title">Create Your First Channel</span></h3>
<p data-i18n="modals.wizard.step2_desc">Channels are where conversations happen. Create one to get started.</p>
<div class="wizard-info-box">
<strong data-i18n="modals.wizard.channel_name_label">Channel name:</strong>
<input type="text" id="wizard-channel-name" class="wizard-input" value="General" placeholder="e.g. General, Gaming, Hangout" data-i18n-placeholder="modals.wizard.channel_name_placeholder">
<span class="wizard-hint" data-i18n="modals.wizard.channel_name_hint">You can create more channels later from the sidebar.</span>
</div>
<div id="wizard-channel-result" style="display:none">
<div class="wizard-check" id="wizard-channel-created">✅ Channel created!</div>
<div class="wizard-code-display">
<span data-i18n="modals.wizard.invite_code">Invite code:</span>
<code id="wizard-channel-code"></code>
<button id="wizard-copy-code" class="wizard-btn-small" data-i18n="modals.wizard.copy_btn">Copy</button>
</div>
<span class="wizard-hint" data-i18n="modals.wizard.share_hint">Share this code with friends so they can join this channel.</span>
</div>
</div>
<!-- Step 3: Connectivity check -->
<div class="wizard-step" id="wizard-step-3" style="display:none">
<h3>🌐 <span data-i18n="modals.wizard.step3_title">Internet Access</span></h3>
<p data-i18n="modals.wizard.step3_desc">Want friends outside your WiFi to connect? Let's check if your server is reachable from the internet.</p>
<div id="wizard-port-checking" style="display:none">
<div class="wizard-spinner"></div>
<span data-i18n="modals.wizard.checking_port">Checking port reachability...</span>
</div>
<div id="wizard-port-result" style="display:none"></div>
<button id="wizard-check-port-btn" class="wizard-btn-secondary">🔍 <span data-i18n="modals.wizard.check_connection_btn">Check My Connection</span></button>
<div class="wizard-info-box" style="margin-top:16px">
<span class="wizard-hint" data-i18n-html="modals.wizard.lan_only_hint"><strong>LAN only?</strong> If your friends are on the same WiFi, you can skip this — it will work automatically.</span>
</div>
</div>
<!-- Step 4: You're ready -->
<div class="wizard-step" id="wizard-step-4" style="display:none">
<h3>🎉 <span data-i18n="modals.wizard.step4_title">You're All Set!</span></h3>
<p data-i18n="modals.wizard.step4_desc">Haven is running and ready for your friends.</p>
<div class="wizard-checklist">
<div class="wizard-check"><span data-i18n="modals.wizard.check_configured">Server configured</span></div>
<div class="wizard-check" id="wizard-summary-channel">✅ First channel created</div>
<div class="wizard-check" id="wizard-summary-port">⏭️ Port check skipped</div>
</div>
<div class="wizard-info-box">
<strong data-i18n="modals.wizard.how_friends_join">How friends join:</strong>
<ol class="wizard-steps-list">
<li>They open <code id="wizard-final-url">https://YOUR_IP:3000</code> in their browser</li>
<li data-i18n="modals.wizard.step_cert">Click "Advanced" → "Proceed" on the certificate warning</li>
<li data-i18n="modals.wizard.step_register">Register a username and password</li>
<li data-i18n="modals.wizard.step_code">Enter the channel invite code you share with them</li>
</ol>
</div>
<div class="wizard-info-box">
<span class="wizard-hint">💡 <span data-i18n-html="modals.wizard.tip"><strong>Tip:</strong> You can always access settings with the ⚙️ gear icon in the sidebar. This wizard won't show again.</span></span>
</div>
</div>
</div>
<div class="wizard-footer">
<button id="wizard-back-btn" class="wizard-btn-secondary" style="display:none" data-i18n="modals.wizard.back_btn">← Back</button>
<div style="flex:1"></div>
<button id="wizard-skip-btn" class="wizard-btn-text" data-i18n="modals.wizard.skip_btn">Skip Setup</button>
<button id="wizard-next-btn" class="wizard-btn-primary" data-i18n="modals.wizard.next_btn">Next →</button>
</div>
</div>
</div>
<!-- Channel Code Settings Modal (Admin) -->
<div class="modal-overlay" id="code-settings-modal" style="display:none">
<div class="modal">
<h3 data-i18n="modals.code_settings.title">Channel Code Settings</h3>
<p class="modal-desc" id="code-settings-channel-name"></p>
<div class="form-group">
<label data-i18n="modals.code_settings.visibility_label">Code Visibility</label>
<select id="code-visibility-select" class="form-select">
<option value="public" data-i18n="modals.code_settings.visibility_public">Public — everyone can see the code</option>
<option value="private" data-i18n="modals.code_settings.visibility_private">Private — only admins see the code</option>
</select>
</div>
<div class="form-group">
<label data-i18n="modals.code_settings.mode_label">Code Mode</label>
<select id="code-mode-select" class="form-select">
<option value="static" data-i18n="modals.code_settings.mode_static">Static — code never changes</option>
<option value="dynamic" data-i18n="modals.code_settings.mode_dynamic">Dynamic — code rotates automatically</option>
</select>
</div>
<div class="form-group" id="rotation-type-group" style="display:none">
<label data-i18n="modals.code_settings.rotation_label">Rotation Trigger</label>
<select id="code-rotation-type-select" class="form-select">
<option value="time" data-i18n="modals.code_settings.rotation_time">Time-based — rotate every X minutes</option>
<option value="joins" data-i18n="modals.code_settings.rotation_joins">Join-based — rotate after X new joins</option>
</select>
</div>
<div class="form-group" id="rotation-interval-group" style="display:none">
<label id="rotation-interval-label" data-i18n="modals.code_settings.interval_label">Rotation Interval (minutes)</label>
<input type="number" id="code-rotation-interval" min="1" max="10000" value="60">
</div>
<div class="modal-actions">
<button class="btn-sm" id="code-settings-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="code-rotate-now-btn">🔄 <span data-i18n="modals.code_settings.rotate_now_btn">Rotate Now</span></button>
<button class="btn-sm btn-accent" id="code-settings-save-btn" data-i18n="modals.common.save">Save</button>
</div>
</div>
</div>
<!-- Add Server Modal -->
<div class="modal-overlay" id="add-server-modal" style="display:none">
<div class="modal">
<h3 id="add-server-modal-title" data-i18n="modals.add_server.title">Add a Server</h3>
<p class="modal-desc" data-i18n="modals.add_server.desc">Connect to a friend's Haven server</p>
<div class="form-group">
<label data-i18n="modals.add_server.name_label">Server Name</label>
<input type="text" id="add-server-name-input" placeholder='e.g. Jake&apos;s Haven' maxlength="30">
</div>
<div class="form-group">
<label data-i18n="modals.add_server.address_label">Server Address</label>
<input type="text" id="server-url-input" placeholder="e.g. https://192.168.1.5:3000" list="known-servers-datalist">
<datalist id="known-servers-datalist"></datalist>
</div>
<div class="form-group">
<label data-i18n="modals.add_server.icon_label">Icon URL</label> <span class="muted-text" data-i18n="modals.add_server.icon_optional">(optional)</span>
<input type="text" id="add-server-icon-input" placeholder="https://... or leave blank">
</div>
<label class="eula-check-row" style="margin-bottom:12px;gap:8px">
<input type="checkbox" id="server-auto-icon" checked>
<span data-i18n="modals.add_server.auto_icon">Auto-pull icon from server</span>
</label>
<div class="modal-actions">
<button class="btn-sm" id="cancel-server-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="save-server-btn" data-i18n="modals.add_server.add_btn">Add Server</button>
</div>
</div>
</div>
<!-- Rename / Profile Modal -->
<div class="modal-overlay" id="rename-modal" style="display:none">
<div class="modal">
<h3 data-i18n="modals.edit_profile.title">Edit Profile</h3>
<!-- Avatar Customizer -->
<div class="avatar-settings" style="margin-bottom: 16px;">
<h5 class="settings-section-subtitle" data-i18n="modals.edit_profile.avatar_title">Avatar</h5>
<div class="avatar-upload-row" style="margin-bottom: 10px;">
<div class="avatar-upload-preview" id="avatar-upload-preview"></div>
<button type="button" class="btn-sm" id="avatar-upload-btn" data-i18n="modals.edit_profile.upload_btn">Upload</button>
<button type="button" class="btn-sm" id="avatar-remove-btn" data-i18n="modals.edit_profile.clear_btn">Clear</button>
<input type="file" id="avatar-file-input" accept="image/jpeg,image/png,image/gif,image/webp" style="display:none">
</div>
<small class="muted-text" style="display:block;margin-top:-4px;margin-bottom:8px;opacity:0.6" data-i18n="modals.edit_profile.avatar_hint">JPG, PNG, GIF (animated!), WebP · Max 2 MB</small>
<h5 class="settings-section-subtitle" data-i18n="modals.edit_profile.shape_title">Shape</h5>
<div class="avatar-shape-picker" id="avatar-shape-picker">
<button type="button" class="avatar-shape-btn active" data-shape="circle" title="Circle"><div class="shape-preview shape-circle"></div></button>
<button type="button" class="avatar-shape-btn" data-shape="rounded" title="Rounded"><div class="shape-preview shape-rounded"></div></button>
<button type="button" class="avatar-shape-btn" data-shape="squircle" title="Squircle"><div class="shape-preview shape-squircle"></div></button>
<button type="button" class="avatar-shape-btn" data-shape="hex" title="Hexagon"><div class="shape-preview shape-hex"></div></button>
<button type="button" class="avatar-shape-btn" data-shape="diamond" title="Diamond"><div class="shape-preview shape-diamond"></div></button>
</div>
</div>
<!-- Display Name -->
<p class="modal-desc" data-i18n="modals.edit_profile.display_name_desc">Letters, numbers, underscores, and spaces (220 chars)</p>
<div class="form-group">
<label data-i18n="modals.edit_profile.display_name_label">Display Name</label>
<input type="text" id="rename-input" data-i18n-placeholder="modals.edit_profile.display_name_placeholder" placeholder="New display name..." maxlength="20">
</div>
<!-- Bio -->
<div class="form-group" style="margin-top:8px">
<label data-i18n="modals.edit_profile.bio_label">Bio</label> <span class="muted-text" data-i18n="modals.edit_profile.bio_max">(max 190 chars)</span>
<textarea id="edit-profile-bio" class="edit-profile-textarea" maxlength="190" data-i18n-placeholder="modals.edit_profile.bio_placeholder" placeholder="Tell people about yourself…" rows="2"></textarea>
</div>
<div class="modal-actions">
<span id="avatar-save-status" class="settings-hint" style="flex:1"></span>
<button class="btn-sm" id="cancel-rename-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="save-rename-btn" data-i18n="modals.common.save">Save</button>
</div>
</div>
</div>
<!-- Admin Action Modal (Kick/Ban/Mute/Delete) -->
<div class="modal-overlay" id="admin-action-modal" style="display:none">
<div class="modal">
<h3 id="admin-action-title" data-i18n="modals.admin_action.title">Moderate User</h3>
<p class="modal-desc" id="admin-action-desc"></p>
<div class="form-group" id="admin-reason-group">
<label data-i18n="modals.admin_action.reason_label">Reason (optional)</label>
<input type="text" id="admin-action-reason" data-i18n-placeholder="modals.admin_action.reason_placeholder" placeholder="Reason..." maxlength="200">
</div>
<div class="form-group" id="admin-duration-group" style="display:none">
<label data-i18n="modals.admin_action.duration_label">Duration (minutes)</label>
<input type="number" id="admin-action-duration" value="10" min="1" max="43200" step="1">
</div>
<div class="form-group" id="admin-scrub-group" style="display:none">
<label class="toggle-row">
<span data-i18n="modals.admin_action.scrub_label">Scrub messages</span>
<input type="checkbox" id="admin-scrub-checkbox">
</label>
<div id="admin-scrub-scope-row" style="display:none;margin-top:6px;">
<label data-i18n="modals.admin_action.scope_label">Scope</label>
<select id="admin-scrub-scope" class="settings-select" style="width:100%;margin-top:4px;">
<option value="channel" data-i18n="modals.admin_action.scope_channel">This channel only</option>
<option value="server" data-i18n="modals.admin_action.scope_server">Server-wide</option>
</select>
</div>
<small class="settings-hint" data-i18n="modals.admin_action.scrub_hint">Removes the user's messages (archived/protected messages are preserved)</small>
</div>
<div class="modal-actions">
<button class="btn-sm" id="cancel-admin-action-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent btn-danger-fill" id="confirm-admin-action-btn" data-i18n="modals.common.confirm">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>⚙️ <span data-i18n="settings.title">Settings</span></h3>
<div class="settings-tab-bar" id="settings-tab-bar">
<button class="settings-tab active" data-tab="user">👤 <span data-i18n="settings.tab.user">User</span></button>
<button class="settings-tab settings-tab-admin" data-tab="admin" style="display:none">🛡️ <span data-i18n="settings.tab.admin">Admin</span></button>
</div>
<button class="settings-close-btn" id="close-settings-btn">&times;</button>
</div>
<div class="settings-layout">
<nav class="settings-nav" id="settings-nav">
<div class="settings-nav-user">
<div class="settings-nav-item active" data-target="section-language">🗣️ <span data-i18n="settings.nav.language">Language</span></div>
<div class="settings-nav-item" data-target="section-density">📐 <span data-i18n="settings.nav.layout">Layout</span></div>
<div class="settings-nav-item" data-target="section-sounds">🔔 <span data-i18n="settings.nav.sounds">Sounds</span></div>
<div class="settings-nav-item" data-target="section-push">📲 <span data-i18n="settings.nav.push">Push</span></div>
<div class="settings-nav-item" data-target="section-password">🔒 <span data-i18n="settings.nav.password">Password</span></div>
<div class="settings-nav-item" data-target="section-2fa">🔐 <span data-i18n="settings.nav.two_factor">Two-Factor</span></div>
<div class="settings-nav-item" data-target="section-recovery">🔑 <span data-i18n="settings.nav.recovery">Recovery</span></div>
<div class="settings-nav-item" data-target="section-delete-account">⚠️ <span data-i18n="settings.nav.account">Account</span></div>
<div class="settings-nav-item" data-target="section-plugins">🧩 <span data-i18n="settings.nav.plugins">Plugins & Themes</span></div>
<div class="settings-nav-item" data-target="section-desktop-shortcuts" id="desktop-shortcuts-nav" style="display:none">⌨️ <span data-i18n="settings.nav.shortcuts">Shortcuts</span></div>
<div class="settings-nav-item" data-target="section-desktop-app" id="desktop-app-nav" style="display:none">🖥️ <span data-i18n="settings.nav.desktop_app">Desktop App</span></div>
</div>
<div class="settings-nav-admin-group" style="display:none">
<div class="settings-nav-item settings-nav-admin" data-target="section-branding" style="display:none">🏠 <span data-i18n="settings.nav.branding">Branding</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-members" style="display:none">👥 <span data-i18n="settings.nav.members">Members</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-whitelist" style="display:none">🛡️ <span data-i18n="settings.nav.whitelist">Whitelist</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-invite" style="display:none">🌐 <span data-i18n="settings.nav.invite">Invite Code</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-cleanup" style="display:none">🗑️ <span data-i18n="settings.nav.cleanup">Cleanup</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-uploads" style="display:none">📁 <span data-i18n="settings.nav.limits">Limits</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-sounds-admin" style="display:none">🔊 <span data-i18n="settings.nav.admin_sounds">Sounds</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-emojis" style="display:none">😎 <span data-i18n="settings.nav.emojis">Emojis</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-roles" style="display:none">👑 <span data-i18n="settings.nav.roles">Roles</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-tunnel" style="display:none">🧭 <span data-i18n="settings.nav.tunnel">Tunnel</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-bots" style="display:none">🤖 <span data-i18n="settings.nav.bots">Bots</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-import" style="display:none">📦 <span data-i18n="settings.nav.import">Import</span></div>
<div class="settings-nav-item settings-nav-admin" data-target="section-modmode" style="display:none">🔧 <span data-i18n="settings.nav.mod_mode">Mod Mode</span></div>
</div>
</nav>
<div class="settings-body" id="settings-body-user">
<!-- Language Section -->
<div class="settings-section" id="section-language">
<h5 class="settings-section-title">🗣️ <span data-i18n="settings.language_section.title">Language</span></h5>
<label class="select-row">
<span data-i18n="settings.language_section.label">Language</span>
<select id="language-select" class="settings-select">
<option value="en">🇬🇧 English</option>
<option value="fr">🇫🇷 Français</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="es">🇪🇸 Español</option>
<option value="ru">🇷🇺 Русский</option>
<option value="zh">🇨🇳 中文</option>
</select>
</label>
<small class="settings-hint" data-i18n="settings.language_section.hint">Changes apply immediately</small>
</div>
<div class="settings-section" id="section-density">
<h5 class="settings-section-title">📐 <span data-i18n="settings.layout_density.title">Layout Density</span></h5>
<div class="density-picker" id="density-picker">
<button type="button" class="density-btn" data-density="compact" data-i18n-title="settings.layout_density.compact_hint" title="Compact — tighter spacing, smaller avatars">
<span class="density-icon">▪️</span>
<span class="density-label" data-i18n="settings.layout_density.compact">Compact</span>
</button>
<button type="button" class="density-btn active" data-density="cozy" data-i18n-title="settings.layout_density.cozy_hint" title="Cozy — default balanced layout">
<span class="density-icon"></span>
<span class="density-label" data-i18n="settings.layout_density.cozy">Cozy</span>
</button>
<button type="button" class="density-btn" data-density="spacious" data-i18n-title="settings.layout_density.spacious_hint" title="Spacious — more breathing room">
<span class="density-icon"></span>
<span class="density-label" data-i18n="settings.layout_density.spacious">Spacious</span>
</button>
</div>
</div>
<!-- Font Size -->
<div class="settings-section" id="section-font-size" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">🔤 <span data-i18n="settings.font_size.title">Font Size</span></h5>
<div class="font-size-picker" id="font-size-picker">
<button type="button" class="density-btn" data-fontsize="small" data-i18n-title="settings.font_size.small_hint" title="Small — 13px base">
<span class="density-icon" style="font-size:11px">A</span>
<span class="density-label" data-i18n="settings.font_size.small">Small</span>
</button>
<button type="button" class="density-btn active" data-fontsize="normal" data-i18n-title="settings.font_size.normal_hint" title="Normal — 15px base (default)">
<span class="density-icon" style="font-size:14px">A</span>
<span class="density-label" data-i18n="settings.font_size.normal">Normal</span>
</button>
<button type="button" class="density-btn" data-fontsize="large" data-i18n-title="settings.font_size.large_hint" title="Large — 17px base">
<span class="density-icon" style="font-size:17px">A</span>
<span class="density-label" data-i18n="settings.font_size.large">Large</span>
</button>
<button type="button" class="density-btn" data-fontsize="x-large" data-i18n-title="settings.font_size.xl_hint" title="Extra Large — 20px base">
<span class="density-icon" style="font-size:20px">A</span>
<span class="density-label" data-i18n="settings.font_size.xl">XL</span>
</button>
</div>
</div>
<!-- Emoji Reaction Size -->
<div class="settings-section" id="section-emoji-size" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">😀 Reaction Size</h5>
<div class="font-size-picker" id="emoji-size-picker">
<button type="button" class="density-btn" data-emojisize="small" title="Small reactions">
<span class="density-icon" style="font-size:11px">😀</span>
<span class="density-label">Small</span>
</button>
<button type="button" class="density-btn active" data-emojisize="normal" title="Normal reactions (default)">
<span class="density-icon" style="font-size:14px">😀</span>
<span class="density-label">Normal</span>
</button>
<button type="button" class="density-btn" data-emojisize="large" title="Large reactions">
<span class="density-icon" style="font-size:18px">😀</span>
<span class="density-label">Large</span>
</button>
<button type="button" class="density-btn" data-emojisize="x-large" title="Extra large reactions">
<span class="density-icon" style="font-size:22px">😀</span>
<span class="density-label">XL</span>
</button>
</div>
</div>
<!-- Role Color Display -->
<div class="settings-section" id="section-role-display" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">🎨 Role Display</h5>
<div class="density-picker" id="role-display-picker">
<button type="button" class="density-btn active" data-roledisplay="colored-name" title="Color the username with the role color">
<span class="density-icon" style="color:#5865f2;font-weight:bold">A</span>
<span class="density-label">Colored Name</span>
</button>
<button type="button" class="density-btn" data-roledisplay="dot" title="Show a colored dot next to the name (legacy)">
<span class="density-icon"></span>
<span class="density-label">Dot</span>
</button>
</div>
<small class="settings-hint" style="margin-top:4px;display:block">How role colors are shown next to usernames in chat and the member list.</small>
</div>
<!-- Toolbar Icon Style -->
<div class="settings-section" id="section-toolbar-icons" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">🎛️ Toolbar Icons</h5>
<div class="density-picker" id="toolbar-icon-picker">
<button type="button" class="density-btn active" data-toolbaricons="mono" title="Use sleek monochrome icons in message toolbars">
<span class="density-icon" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<circle cx="12" cy="12" r="9"></circle>
<path d="M8.5 14.5c1 1.2 2.2 1.8 3.5 1.8s2.5-.6 3.5-1.8" stroke-linecap="round"></path>
<circle cx="9.2" cy="10.2" r="1" fill="currentColor" stroke="none"></circle>
<circle cx="14.8" cy="10.2" r="1" fill="currentColor" stroke="none"></circle>
</svg>
</span>
<span class="density-label">Monochrome</span>
</button>
<button type="button" class="density-btn" data-toolbaricons="emoji" title="Use colorful emoji icons in message toolbars">
<span class="density-icon">😀</span>
<span class="density-label">Colorful Emoji</span>
</button>
</div>
<label class="toolbar-slots-row" for="toolbar-visible-slots">
<span>Visible toolbar slots before overflow</span>
<input type="range" id="toolbar-visible-slots" min="1" max="7" step="1" value="3">
<span id="toolbar-visible-slots-value" class="toolbar-slots-value">3</span>
</label>
<div class="toolbar-order-wrap">
<div class="toolbar-order-head">
<span>Toolbar slot order</span>
<button type="button" class="btn-sm" id="toolbar-order-reset-btn">Reset</button>
</div>
<div id="toolbar-order-list" class="toolbar-order-list"></div>
</div>
<small class="settings-hint" style="margin-top:4px;display:block">Applies to message and thread hover action bars.</small>
</div>
<!-- Image Display Mode -->
<div class="settings-section" id="section-density" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">🖼️ <span data-i18n="settings.image_display.title">Image Display</span></h5>
<div class="density-picker" id="image-mode-picker">
<button type="button" class="density-btn active" data-image-mode="thumbnail" data-i18n-title="settings.image_display.thumbnail_hint" title="Thumbnail — small preview, click to enlarge">
<span class="density-icon">🔍</span>
<span class="density-label" data-i18n="settings.image_display.thumbnail">Thumbnail</span>
</button>
<button type="button" class="density-btn" data-image-mode="full" data-i18n-title="settings.image_display.full_hint" title="Full — large inline images like Discord">
<span class="density-icon">🖼️</span>
<span class="density-label" data-i18n="settings.image_display.full">Full</span>
</button>
</div>
</div>
<!-- Banner Display (client-side) -->
<div class="settings-section" id="section-banner-display" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px; display:none;">
<h5 class="settings-section-title">🏞️ Banner Display</h5>
<label style="margin-top:4px;display:flex;align-items:center;gap:8px;font-size:12px">
<span style="white-space:nowrap">Banner height</span>
<input type="range" id="banner-height-slider" min="80" max="400" step="10" value="180" style="flex:1">
<span id="banner-height-value" style="min-width:36px;text-align:right;font-variant-numeric:tabular-nums">180px</span>
</label>
<label style="margin-top:4px;display:flex;align-items:center;gap:8px;font-size:12px">
<span style="white-space:nowrap">Vertical offset</span>
<input type="range" id="banner-offset-slider" min="0" max="100" step="1" value="0" style="flex:1">
<span id="banner-offset-value" style="min-width:30px;text-align:right;font-variant-numeric:tabular-nums">0%</span>
</label>
<label class="select-row" style="margin-top:8px">
<span>Header style</span>
<select id="banner-header-mode" style="max-width:160px">
<option value="full">Full Header (opaque)</option>
<option value="shaded">Shaded Header</option>
<option value="minimal">Minimal Header</option>
<option value="transparent">Transparent Header</option>
</select>
</label>
<small class="settings-hint">How the banner interacts with the channel header area. These settings are per-device.</small>
</div>
<!-- Chat Behavior -->
<div class="settings-section" id="section-chat-behavior" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">💬 Chat</h5>
<label class="toggle-row">
<span>Up Arrow Edits Last Message</span>
<input type="checkbox" id="up-arrow-edit" checked>
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px">Press the Up arrow key on an empty input to quickly edit your last message.</small>
</div>
<!-- Status Bar -->
<div class="settings-section" id="section-statusbar" style="border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: 8px;">
<h5 class="settings-section-title">📊 Status Bar</h5>
<label class="toggle-row">
<span>Show Status Bar</span>
<input type="checkbox" id="show-status-bar">
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px">Show the status bar at the bottom of the screen with server, ping, channel, and online info.</small>
</div>
<!-- Sounds Section -->
<div class="settings-section" id="section-sounds">
<h5 class="settings-section-title">🔔 <span data-i18n="settings.sounds_section.title">Sounds</span></h5>
<div class="notif-settings">
<label class="toggle-row">
<span data-i18n="settings.sounds_section.notifications">Notifications</span>
<input type="checkbox" id="notif-enabled">
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px">General message sounds (off by default). @Mentions, replies, and DMs always notify.</small>
<label class="toggle-row">
<span>@Mentions</span>
<input type="checkbox" id="notif-mentions-enabled" checked>
</label>
<label class="toggle-row">
<span>Replies</span>
<input type="checkbox" id="notif-replies-enabled" checked>
</label>
<label class="toggle-row">
<span>Direct Messages</span>
<input type="checkbox" id="notif-dm-enabled" checked>
</label>
<label class="toggle-row">
<span data-i18n="settings.sounds_section.auto_accept_streams">Auto-accept Streams</span>
<input type="checkbox" id="auto-accept-streams" checked>
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px" data-i18n="settings.sounds_section.auto_accept_streams_hint">Automatically open screen shares when someone starts streaming. Turn off to join manually.</small>
<label class="toggle-row">
<span data-i18n="settings.sounds_section.hide_voice_panel">Hide Voice Panel</span>
<input type="checkbox" id="hide-voice-panel">
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px" data-i18n="settings.sounds_section.hide_voice_panel_hint">Hide the voice users panel on desktop. Voice users are still shown in channel indicators.</small>
<label class="toggle-row">
<span data-i18n="settings.sounds_section.sidebar_voice_controls">Sidebar Voice Controls</span>
<input type="checkbox" id="sidebar-voice-controls">
</label>
<small class="settings-hint" style="display:block;margin-bottom:6px" data-i18n="settings.sounds_section.sidebar_voice_controls_hint">Show mute/deafen buttons in the sidebar instead of the voice panel header.</small>
<label class="volume-row">
<span data-i18n="settings.sounds_section.volume">Volume</span>
<input type="range" id="notif-volume" min="0" max="100" value="50" class="slider-sm">
</label>
<label class="select-row">
<span data-i18n="settings.sounds_section.messages">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>
<label class="select-row">
<span data-i18n="settings.sounds_section.msg_sent">Msg Sent</span>
<select id="notif-sent-sound">
<option value="swoosh">Swoosh</option>
<option value="blip">Blip</option>
<option value="ping">Ping</option>
<option value="chime">Chime</option>
<option value="drop">Drop</option>
<option value="none">None</option>
</select>
</label>
<div class="notif-divider"></div>
<label class="volume-row">
<span data-i18n="settings.sounds_section.mention_vol">@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 data-i18n="settings.sounds_section.mentions">@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="volume-row">
<span data-i18n="settings.sounds_section.reply_vol">Reply Vol</span>
<input type="range" id="notif-reply-volume" min="0" max="100" value="80" class="slider-sm">
</label>
<label class="select-row">
<span data-i18n="settings.sounds_section.replies">Replies</span>
<select id="notif-reply-sound">
<option value="chime">Chime</option>
<option value="bell">Bell</option>
<option value="alert">Alert</option>
<option value="chord">Chord</option>
<option value="ping">Ping</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="volume-row">
<span data-i18n="settings.sounds_section.join_vol">Join Vol</span>
<input type="range" id="notif-join-volume" min="0" max="100" value="80" class="slider-sm">
</label>
<label class="select-row">
<span data-i18n="settings.sounds_section.user_joined">User Joined</span>
<select id="notif-join-sound">
<option value="chime">Chime</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>
<div class="notif-divider"></div>
<label class="volume-row">
<span data-i18n="settings.sounds_section.leave_vol">Leave Vol</span>
<input type="range" id="notif-leave-volume" min="0" max="100" value="80" class="slider-sm">
</label>
<label class="select-row">
<span data-i18n="settings.sounds_section.user_left">User Left</span>
<select id="notif-leave-sound">
<option value="drop">Drop</option>
<option value="chime">Chime</option>
<option value="ping">Ping</option>
<option value="blip">Blip</option>
<option value="none">None</option>
</select>
</label>
<div style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<button class="btn-sm btn-full btn-accent" id="open-sound-manager-user-btn">🎵 <span data-i18n="settings.sounds_section.sound_manager_btn">Sound Manager</span></button>
<small class="settings-hint" style="display:block;margin-top:4px" data-i18n="settings.sounds_section.sound_manager_hint">Browse sounds, assign to events, set hotkeys</small>
</div>
</div>
</div>
<!-- Push Notifications Section -->
<div class="settings-section" id="section-push">
<h5 class="settings-section-title">📲 <span data-i18n="settings.push_section.title">Push Notifications</span></h5>
<div class="notif-settings">
<label class="toggle-row">
<span data-i18n="settings.push_section.enable">Enable Push</span>
<input type="checkbox" id="push-notif-enabled">
</label>
<small class="settings-hint" id="push-notif-status" data-i18n="settings.push_section.checking">Checking...</small>
<small class="settings-hint" style="opacity:0.5" data-i18n="settings.push_section.hint">Receive notifications even when Haven is closed</small>
</div>
</div>
<!-- Password Change Section -->
<div class="settings-section" id="section-password">
<h5 class="settings-section-title">🔒 <span data-i18n="settings.password_section.title">Password</span></h5>
<div class="password-change-form">
<div class="form-group compact">
<input type="password" id="current-password" data-i18n-placeholder="settings.password_section.current_placeholder" placeholder="Current password" maxlength="128" autocomplete="current-password">
</div>
<div class="form-group compact">
<input type="password" id="new-password" data-i18n-placeholder="settings.password_section.new_placeholder" placeholder="New password (8+ chars)" maxlength="128" autocomplete="new-password">
</div>
<div class="form-group compact">
<input type="password" id="confirm-password" data-i18n-placeholder="settings.password_section.confirm_placeholder" placeholder="Confirm new password" maxlength="128" autocomplete="new-password">
</div>
<button class="btn-sm btn-full" id="change-password-btn" data-i18n="settings.password_section.change_btn">Change Password</button>
<small class="settings-hint" id="password-status"></small>
</div>
</div>
<!-- Two-Factor Authentication Section -->
<div class="settings-section" id="section-2fa">
<h5 class="settings-section-title">🔐 <span data-i18n="settings.two_factor_section.title">Two-Factor Authentication</span></h5>
<!-- Status display -->
<div id="totp-status-area">
<p class="settings-hint" id="totp-status-text"></p>
</div>
<!-- Enable flow (shown when TOTP is OFF) -->
<div id="totp-enable-area" style="display:none">
<p class="settings-hint" style="margin-bottom:10px" data-i18n="settings.two_factor_section.enable_hint">Protect your account with a TOTP authenticator app (Google Authenticator, Authy, etc.).</p>
<button class="btn-sm btn-full" id="totp-enable-btn" data-i18n="settings.two_factor_section.enable_btn">Enable Two-Factor Auth</button>
</div>
<!-- Setup flow (shown after clicking Enable) -->
<div id="totp-setup-area" style="display:none">
<p class="settings-hint" style="margin-bottom:8px" data-i18n="settings.two_factor_section.setup_hint">Scan this QR code with your authenticator app:</p>
<div style="text-align:center;margin:12px 0">
<img id="totp-qr-img" alt="QR Code" style="max-width:200px;border-radius:8px;background:#fff;padding:8px">
</div>
<p class="settings-hint" style="margin-bottom:4px" data-i18n="settings.two_factor_section.manual_hint">Or enter this secret manually:</p>
<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px">
<code id="totp-secret-text" style="font-size:14px;letter-spacing:2px;user-select:all;flex:1;word-break:break-all"></code>
<button class="btn-sm" id="totp-copy-secret" data-i18n-title="settings.two_factor_section.copy_secret_title" title="Copy secret" style="white-space:nowrap">📋 <span data-i18n="settings.two_factor_section.copy_secret_btn">Copy</span></button>
</div>
<div class="form-group compact">
<input type="text" id="totp-verify-code" data-i18n-placeholder="settings.two_factor_section.verify_placeholder" placeholder="Enter 6-digit code to confirm" maxlength="6" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" style="text-align:center;font-size:18px;letter-spacing:6px">
</div>
<button class="btn-sm btn-full" id="totp-verify-setup-btn" data-i18n="settings.two_factor_section.verify_btn">Verify & Activate</button>
<button class="btn-sm btn-full" id="totp-cancel-setup-btn" data-i18n="modals.common.cancel" style="margin-top:6px;opacity:0.7">Cancel</button>
<small class="settings-hint" id="totp-setup-status"></small>
</div>
<!-- Backup codes display (shown after successful setup) -->
<div id="totp-backup-area" style="display:none">
<h5 style="margin-bottom:6px">🗝️ <span data-i18n="settings.two_factor_section.backup_title">Backup Codes</span></h5>
<p class="settings-hint" style="margin-bottom:8px" data-i18n="settings.two_factor_section.backup_hint">Save these codes somewhere safe. Each can be used once if you lose access to your authenticator.</p>
<div id="totp-backup-codes" style="font-family:monospace;font-size:15px;line-height:2;background:var(--bg-primary);padding:12px;border-radius:8px;text-align:center;user-select:all"></div>
<button class="btn-sm btn-full" id="totp-copy-backup-btn" style="margin-top:8px">📋 <span data-i18n="settings.two_factor_section.copy_backup_btn">Copy Backup Codes</span></button>
<button class="btn-sm btn-full" id="totp-backup-done-btn" data-i18n="settings.two_factor_section.done_btn" style="margin-top:6px">Done</button>
</div>
<!-- Manage area (shown when TOTP is ON) -->
<div id="totp-manage-area" style="display:none">
<p class="settings-hint" style="color:var(--accent);margin-bottom:6px"><span data-i18n="settings.two_factor_section.enabled_text">Two-factor authentication is enabled</span></p>
<p class="settings-hint" id="totp-backup-remaining" style="margin-bottom:12px"></p>
<div class="form-group compact">
<input type="password" id="totp-disable-password" data-i18n-placeholder="settings.two_factor_section.disable_password_placeholder" placeholder="Enter password to modify" maxlength="128" autocomplete="current-password">
</div>
<div style="display:flex;gap:8px">
<button class="btn-sm btn-danger-fill" id="totp-disable-btn" data-i18n="settings.two_factor_section.disable_btn" style="flex:1">Disable 2FA</button>
<button class="btn-sm" id="totp-regen-backup-btn" data-i18n="settings.two_factor_section.regen_backup_btn" style="flex:1">Regenerate Backup Codes</button>
</div>
<small class="settings-hint" id="totp-manage-status"></small>
</div>
</div>
<!-- Account Recovery Codes Section -->
<div class="settings-section" id="section-recovery">
<h5 class="settings-section-title">🔑 <span data-i18n="settings.recovery_section.title">Account Recovery</span></h5>
<p class="settings-hint" style="margin-bottom:8px" data-i18n="settings.recovery_section.desc">Generate one-time recovery codes to regain access if you forget your password. Store them somewhere safe — they work like a last-resort key.</p>
<div class="settings-warning-box" id="recovery-e2e-warning" style="background:rgba(231,76,60,0.12);border:1px solid rgba(231,76,60,0.4);border-radius:8px;padding:10px 12px;margin-bottom:12px;font-size:0.82rem;color:var(--text-secondary)">
<span data-i18n-html="settings.recovery_section.e2e_warning">⚠️ <strong>Important:</strong> Using a recovery code to reset your password will permanently and irreversibly destroy your end-to-end encryption keys. All encrypted direct message history will become unreadable. There is no way to undo this.</span>
</div>
<!-- Status -->
<p class="settings-hint" id="recovery-code-status" style="margin-bottom:10px"></p>
<!-- Generate flow -->
<div id="recovery-generate-area">
<div class="form-group compact">
<input type="password" id="recovery-gen-password" data-i18n-placeholder="settings.recovery_section.confirm_password_placeholder" placeholder="Confirm your password" maxlength="128" autocomplete="current-password">
</div>
<button class="btn-sm btn-full" id="recovery-generate-btn" data-i18n="settings.recovery_section.generate_btn">Generate Recovery Codes</button>
<small class="settings-hint" id="recovery-gen-status"></small>
</div>
<!-- Codes display (shown after generation) -->
<div id="recovery-codes-area" style="display:none">
<p class="settings-hint" style="margin-bottom:8px" data-i18n="settings.recovery_section.save_hint">Save these codes now — they will not be shown again. Each code can only be used once.</p>
<div id="recovery-codes-list" style="font-family:monospace;font-size:15px;line-height:2;background:var(--bg-primary);padding:12px;border-radius:8px;text-align:center;user-select:all"></div>
<button class="btn-sm btn-full" id="recovery-copy-btn" style="margin-top:8px">📋 <span data-i18n="settings.recovery_section.copy_codes_btn">Copy Codes</span></button>
<button class="btn-sm btn-full" id="recovery-codes-done-btn" data-i18n="settings.recovery_section.done_btn" style="margin-top:6px">Done</button>
</div>
</div>
<!-- Delete Account Section -->
<div class="settings-section" id="section-delete-account">
<h5 class="settings-section-title">⚠️ <span data-i18n="settings.delete_account_section.title">Delete Account</span></h5>
<p class="settings-hint" style="margin-bottom:8px;" data-i18n="settings.delete_account_section.desc">Permanently delete your account. This cannot be undone.</p>
<button class="btn-sm btn-danger-fill" id="delete-account-btn" data-i18n="settings.delete_account_section.btn">Delete My Account</button>
</div>
<!-- Plugins & Themes Section -->
<div class="settings-section" id="section-plugins">
<h5 class="settings-section-title">🧩 <span data-i18n="settings.plugins_section.title">Plugins & Themes</span></h5>
<p class="settings-hint" data-i18n-html="settings.plugins_section.hint">Drop <code>.plugin.js</code> files into the <code>plugins/</code> folder and <code>.theme.css</code> files into the <code>themes/</code> folder, then refresh.</p>
<div style="display:flex;gap:8px;margin-bottom:12px;">
<button class="btn-sm btn-accent" id="plugin-refresh-btn">🔄 <span data-i18n="settings.plugins_section.refresh_btn">Refresh</span></button>
</div>
<h6 style="margin:10px 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted)" data-i18n="settings.plugins_section.plugins_label">Plugins</h6>
<div id="plugin-list" class="plugin-list"></div>
<h6 style="margin:14px 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted)" data-i18n="settings.plugins_section.themes_label">Themes</h6>
<div id="theme-list" class="plugin-list"></div>
</div>
<!-- Desktop Shortcuts Section (only shown in Haven Desktop) -->
<div class="settings-section" id="section-desktop-shortcuts" style="display:none">
<h5 class="settings-section-title">⌨️ <span data-i18n="settings.desktop_shortcuts_section.title">Desktop Shortcuts</span></h5>
<p class="settings-desc" data-i18n="settings.desktop_shortcuts_section.desc">Global keyboard shortcuts — active even when Haven is in the background. Click a row to rebind, or "Clear" to disable.</p>
<div class="shortcut-table" id="shortcut-table">
<div class="shortcut-row" id="shortcut-row-mute">
<span class="shortcut-label" data-i18n="settings.desktop_shortcuts_section.mute_unmute">Mute / Unmute</span>
<span class="shortcut-key" id="shortcut-key-mute"></span>
<div class="shortcut-btns">
<button class="btn-xs shortcut-record-btn" data-action="mute" data-i18n="settings.desktop_shortcuts_section.change_btn">Change</button>
<button class="btn-xs shortcut-clear-btn" data-action="mute" data-i18n="settings.desktop_shortcuts_section.clear_btn">Clear</button>
</div>
</div>
<div class="shortcut-row" id="shortcut-row-deafen">
<span class="shortcut-label" data-i18n="settings.desktop_shortcuts_section.deafen_undeafen">Deafen / Undeafen</span>
<span class="shortcut-key" id="shortcut-key-deafen"></span>
<div class="shortcut-btns">
<button class="btn-xs shortcut-record-btn" data-action="deafen" data-i18n="settings.desktop_shortcuts_section.change_btn">Change</button>
<button class="btn-xs shortcut-clear-btn" data-action="deafen" data-i18n="settings.desktop_shortcuts_section.clear_btn">Clear</button>
</div>
</div>
<div class="shortcut-row" id="shortcut-row-ptt">
<span class="shortcut-label"><span data-i18n="settings.desktop_shortcuts_section.ptt">Push-to-Talk</span> <span class="muted-text" style="font-weight:400;font-size:0.85em" data-i18n="settings.desktop_shortcuts_section.ptt_toggle">(toggle)</span></span>
<span class="shortcut-key" id="shortcut-key-ptt" data-i18n="settings.desktop_shortcuts_section.not_set">Not set</span>
<div class="shortcut-btns">
<button class="btn-xs shortcut-record-btn" data-action="ptt" data-i18n="settings.desktop_shortcuts_section.change_btn">Change</button>
<button class="btn-xs shortcut-clear-btn" data-action="ptt" data-i18n="settings.desktop_shortcuts_section.clear_btn">Clear</button>
</div>
</div>
</div>
<p class="settings-hint" id="shortcut-status" style="margin-top:8px"></p>
</div>
<!-- Desktop App Settings Section (only shown in Haven Desktop) -->
<div class="settings-section" id="section-desktop-app" style="display:none">
<h5 class="settings-section-title">🖥️ <span data-i18n="settings.desktop_app_section.title">Desktop App</span></h5>
<p class="settings-desc" data-i18n="settings.desktop_app_section.desc">Settings specific to Haven Desktop.</p>
<div class="desktop-pref-row">
<label class="toggle-row">
<input type="checkbox" id="pref-start-on-login">
<span class="toggle-label" data-i18n="settings.desktop_app_section.start_on_login">Start on Login</span>
</label>
<p class="settings-hint" data-i18n="settings.desktop_app_section.start_on_login_hint">Launch Haven Desktop automatically when you log in to your computer.</p>
</div>
<div class="desktop-pref-row" id="pref-start-hidden-row" style="display:none">
<label class="toggle-row">
<input type="checkbox" id="pref-start-hidden">
<span class="toggle-label" data-i18n="settings.desktop_app_section.start_hidden">Start Hidden to Tray</span>
</label>
<p class="settings-hint" data-i18n="settings.desktop_app_section.start_hidden_hint">When launching on login, start minimized to the system tray instead of showing the window.</p>
</div>
<div class="desktop-pref-row">
<label class="toggle-row">
<input type="checkbox" id="pref-minimize-to-tray">
<span class="toggle-label" data-i18n="settings.desktop_app_section.minimize_to_tray">Minimize to Tray on Close</span>
</label>
<p class="settings-hint" data-i18n="settings.desktop_app_section.minimize_to_tray_hint">Closing the window hides Haven to the system tray instead of quitting.</p>
</div>
<div class="desktop-pref-row">
<label class="toggle-row">
<input type="checkbox" id="pref-hide-menu-bar">
<span class="toggle-label" data-i18n="settings.desktop_app_section.hide_menu_bar">Hide Menu Bar</span>
</label>
<p class="settings-hint" data-i18n="settings.desktop_app_section.hide_menu_bar_hint">Hide the File / Edit / View / Window / Help toolbar. Press Alt to temporarily show it.</p>
</div>
<div class="desktop-pref-row">
<label class="toggle-row">
<input type="checkbox" id="pref-force-sdr">
<span class="toggle-label" data-i18n="settings.desktop_app_section.force_sdr">Force SDR (sRGB) Color</span>
</label>
<p class="settings-hint" data-i18n="settings.desktop_app_section.force_sdr_hint">Fix washed-out or over-saturated colors on HDR monitors. Requires restart.</p>
</div>
<div class="desktop-pref-row" style="margin-top:16px;padding-top:12px;border-top:1px solid var(--border)">
<span class="settings-hint" id="desktop-version-info" style="opacity:0.6"></span>
</div>
</div>
</div><!-- /settings-body-user -->
<!-- Admin settings body (separate tab) -->
<div class="settings-body" id="settings-body-admin" style="display:none">
<!-- Admin Section (hidden for non-admins) -->
<div class="settings-section admin-settings-section" id="admin-mod-panel" style="display:none">
<!-- Server Branding -->
<div class="admin-settings" id="section-branding">
<h5 class="settings-section-subtitle">🏠 <span data-i18n="settings.admin.branding_title">Server Branding</span></h5>
<label class="select-row">
<span data-i18n="settings.admin.server_name">Server Name</span>
<input type="text" id="server-name-input" maxlength="32" class="settings-text-input" style="max-width:160px" placeholder="HAVEN">
</label>
<label class="select-row">
<span data-i18n="settings.admin.login_title">Login Title</span>
<input type="text" id="server-title-input" maxlength="40" class="settings-text-input" style="max-width:200px" placeholder="(shown on login page)">
</label>
<small class="settings-hint" style="margin-top:2px;display:block" data-i18n="settings.admin.login_title_hint">Custom title displayed on the login screen below the HAVEN logo.</small>
<!-- Server Icon -->
<div class="settings-group" style="margin-top:12px">
<small class="settings-group-label" data-i18n="settings.admin.server_icon_hint">Server icon (square, max 2 MB)</small>
<div class="server-icon-upload-area">
<div class="server-icon-preview" id="server-icon-preview">
<span class="server-icon-text"></span>
</div>
<div class="server-icon-upload-controls">
<input type="file" id="server-icon-file" accept="image/*" style="font-size:11px;max-width:160px">
<div style="display:flex;gap:4px;margin-top:4px">
<button class="btn-sm btn-accent" id="server-icon-upload-btn" data-i18n="settings.admin.upload_btn">Upload</button>
<button class="btn-sm" id="server-icon-remove-btn" data-i18n="settings.admin.remove_btn">Remove</button>
</div>
</div>
</div>
</div>
<!-- Server Banner -->
<div class="settings-group" style="margin-top:12px">
<small class="settings-group-label">Server banner (wide, max 4 MB)</small>
<div class="server-banner-upload-area">
<div class="server-banner-preview" id="server-banner-preview">
<span class="muted-text" style="font-size:11px">No banner</span>
</div>
<div class="server-banner-upload-controls">
<input type="file" id="server-banner-file" accept="image/jpeg,image/png,image/gif,image/webp" style="font-size:11px;max-width:160px">
<div style="display:flex;gap:4px;margin-top:4px">
<button class="btn-sm btn-accent" id="server-banner-upload-btn">Upload</button>
<button class="btn-sm" id="server-banner-remove-btn">Remove</button>
</div>
</div>
</div>
<small class="settings-hint">Banner display settings (height, offset, header style) are in User Settings.</small>
</div>
<!-- Theme & Welcome -->
<div class="settings-group" style="margin-top:12px">
<small class="settings-group-label">Appearance &amp; Welcome</small>
<label class="select-row">
<span data-i18n="settings.admin.default_theme">Default Theme</span>
<select id="default-theme-select" style="max-width:160px">
<option value="" data-i18n="settings.admin.no_theme">None (user's choice)</option>
<option value="haven">Haven</option>
<option value="discord">Discord</option>
<option value="matrix">Matrix</option>
<option value="fallout">Fallout</option>
<option value="ffx">Final Fantasy</option>
<option value="ice">Ice</option>
<option value="nord">Nord</option>
<option value="darksouls">Dark Souls</option>
<option value="eldenring">Elden Ring</option>
<option value="bloodborne">Bloodborne</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="lotr">Lord of the Rings</option>
<option value="abyss">Abyss</option>
<option value="scripture">Scripture</option>
<option value="chapel">Chapel</option>
<option value="gospel">Gospel</option>
<option value="tron">Tron</option>
<option value="halo">Halo</option>
<option value="dracula">Dracula</option>
<option value="win95">Windows 95</option>
</select>
</label>
<small class="settings-hint" data-i18n="settings.admin.default_theme_hint">New users see this theme on first visit. They can still pick their own.</small>
<label class="select-row" style="margin-top:8px">
<span>Welcome Message</span>
<input type="text" id="welcome-message-input" maxlength="500" class="settings-text-input" style="max-width:280px" placeholder="e.g. Welcome {user}!">
</label>
<small class="settings-hint">Shown when a new user joins a channel. Use <code>{user}</code> for the username. Leave blank to disable.</small>
</div>
</div>
<div class="admin-settings" id="section-members" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<label class="select-row">
<span data-i18n="settings.admin.show_members">Show Members</span>
<select id="member-visibility-select">
<option value="online" data-i18n="settings.admin.members_online_only">Online Only</option>
<option value="all" data-i18n="settings.admin.members_all">All Members</option>
<option value="none" data-i18n="settings.admin.members_hidden">Hidden</option>
</select>
</label>
<button class="btn-sm btn-full" id="view-bans-btn">📋 <span data-i18n="settings.admin.view_bans_btn">View Bans</span></button>
<button class="btn-sm btn-full" id="view-deleted-users-btn" style="margin-top:6px">🗑️ <span data-i18n="settings.admin.view_deleted_btn">View Deleted Users</span></button>
<button class="btn-sm btn-full" id="view-all-members-btn" style="margin-top:6px">👥 <span data-i18n="settings.admin.view_all_members_btn">View All Members</span></button>
<label class="toggle-row" style="margin-top:8px">
<span data-i18n="settings.admin.update_banner_label">Update banner (admins only)</span>
<input type="checkbox" id="update-banner-admin-only">
</label>
<small class="settings-hint" data-i18n="settings.admin.update_banner_hint">When enabled, the "update available" banner is only shown to admins</small>
</div>
<div class="admin-settings" id="section-whitelist" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🛡️ <span data-i18n="settings.admin.whitelist_title">Whitelist</span></h5>
<label class="toggle-row">
<span data-i18n="settings.admin.whitelist_enabled">Enabled</span>
<input type="checkbox" id="whitelist-enabled">
</label>
<small class="settings-hint" data-i18n="settings.admin.whitelist_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" data-i18n-placeholder="settings.admin.whitelist_username_placeholder" placeholder="Username..." maxlength="20" class="settings-text-input">
<button class="btn-sm btn-accent" id="whitelist-add-btn" data-i18n="settings.admin.whitelist_add_btn">Add</button>
</div>
<div id="whitelist-list" class="whitelist-list">
<p class="muted-text" data-i18n="modals.common.loading">Loading...</p>
</div>
</div>
</div>
<div class="admin-settings" id="section-invite" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🌐 <span data-i18n="settings.admin.invite_title">Server Invite Code</span></h5>
<small class="settings-hint" data-i18n="settings.admin.invite_hint">A single code that adds people to every channel &amp; sub-channel on the server</small>
<div style="margin-top:8px;">
<div id="server-code-display" style="display:flex;align-items:center;gap:6px;margin-bottom:6px;">
<code id="server-code-value" style="font-family:monospace;font-size:1rem;letter-spacing:0.05em;opacity:0.7"></code>
<button class="btn-sm" id="copy-server-code-btn" data-i18n-title="settings.admin.copy_code_title" title="Copy code">📋</button>
</div>
<div style="display:flex;gap:4px;">
<button class="btn-sm btn-accent" id="generate-server-code-btn" data-i18n="settings.admin.invite_generate_btn">Generate</button>
<button class="btn-sm" id="clear-server-code-btn" data-i18n="settings.admin.invite_clear_btn">Clear</button>
</div>
</div>
<div style="margin-top:12px;">
<label class="select-row">
<span>Vanity Invite Link</span>
<input type="text" id="vanity-code-input" maxlength="32" class="settings-text-input" style="max-width:160px" placeholder="my-server">
</label>
<small class="settings-hint">Custom invite slug (3-32 chars, letters, numbers, hyphens, underscores). People can join via <code>/invite/your-slug</code></small>
<div style="display:flex;gap:4px;margin-top:4px">
<button class="btn-sm btn-accent" id="vanity-code-save-btn">Save</button>
<button class="btn-sm" id="vanity-code-clear-btn">Clear</button>
</div>
</div>
</div>
<div class="admin-settings" id="section-cleanup" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🗑️ <span data-i18n="settings.admin.cleanup_title">Auto-Cleanup</span></h5>
<label class="toggle-row">
<span data-i18n="settings.admin.cleanup_enabled">Enabled</span>
<input type="checkbox" id="cleanup-enabled">
</label>
<label class="select-row">
<span data-i18n="settings.admin.cleanup_max_age">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" data-i18n="settings.admin.cleanup_age_hint">Delete messages older than N days (0 = off)</small>
<label class="select-row">
<span data-i18n="settings.admin.cleanup_max_size">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" data-i18n="settings.admin.cleanup_size_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;">🧹 <span data-i18n="settings.admin.cleanup_run_btn">Run Cleanup Now</span></button>
</div>
<div class="admin-settings" id="section-uploads" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">📁 <span data-i18n="settings.admin.uploads_title">Uploads &amp; Limits</span></h5>
<label class="select-row">
<span data-i18n="settings.admin.max_upload">Max Upload Size (MB)</span>
<input type="number" id="max-upload-mb" min="1" max="2048" value="25" class="settings-number-input">
</label>
<small class="settings-hint" data-i18n="settings.admin.max_upload_hint">Maximum file size users can upload (12048 MB, default 25)</small>
<label class="select-row" style="margin-top:8px">
<span data-i18n="settings.admin.max_sound">Max Sound File Size (KB)</span>
<input type="number" id="max-sound-kb" min="256" max="10240" value="1024" class="settings-number-input">
</label>
<small class="settings-hint" data-i18n="settings.admin.max_sound_hint">Max soundboard upload size (25610240 KB, default 1024 / 1 MB)</small>
<label class="select-row" style="margin-top:8px">
<span data-i18n="settings.admin.max_emoji">Max Emoji File Size (KB)</span>
<input type="number" id="max-emoji-kb" min="64" max="1024" value="256" class="settings-number-input">
</label>
<small class="settings-hint" data-i18n="settings.admin.max_emoji_hint">Max custom emoji upload size (641024 KB, default 256)</small>
<label class="select-row" style="margin-top:8px">
<span data-i18n="settings.admin.max_poll_options">Max Poll Options</span>
<input type="number" id="max-poll-options" min="2" max="25" value="10" class="settings-number-input">
</label>
<small class="settings-hint" data-i18n="settings.admin.max_poll_hint">Maximum answer choices per poll (225, default 10)</small>
</div>
<div class="admin-settings" id="section-sounds-admin" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🔊 <span data-i18n="settings.admin.sounds_title">Custom Sounds</span></h5>
<small class="settings-hint" data-i18n="settings.admin.sounds_hint">Upload audio files for custom notification sounds</small>
<div style="margin-top:8px;">
<button class="btn-sm btn-full btn-accent" id="open-sound-manager-btn">⚙️ <span data-i18n="settings.admin.manage_sounds_btn">Manage Sounds</span></button>
</div>
</div>
<div class="admin-settings" id="section-emojis" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">😎 <span data-i18n="settings.admin.emojis_title">Custom Emojis</span></h5>
<small class="settings-hint" data-i18n="settings.admin.emojis_hint">Upload images for custom server emojis</small>
<div style="margin-top:8px;">
<button class="btn-sm btn-full btn-accent" id="open-emoji-manager-btn">⚙️ <span data-i18n="settings.admin.manage_emojis_btn">Manage Emojis</span></button>
</div>
</div>
<!-- Role Management (Admin only) -->
<div class="admin-settings" id="section-roles" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">👑 <span data-i18n="settings.admin.roles_title">Role Management</span></h5>
<small class="settings-hint" data-i18n="settings.admin.roles_hint">Create and manage roles. Assign roles to users from the user list context menu.</small>
<div style="margin-top:8px;display:flex;gap:6px">
<button class="btn-sm btn-full btn-accent" id="open-role-editor-btn">⚙️ <span data-i18n="settings.admin.manage_roles_btn">Manage Roles</span></button>
<button class="btn-sm" id="reset-roles-btn" title="Reset all roles to deployment defaults">🔄 <span data-i18n="settings.admin.reset_roles_btn">Reset to Default</span></button>
</div>
<div class="settings-group" style="margin-top:12px">
<small class="settings-group-label">Role Icon Visibility</small>
<label class="toggle-row">
<span>Show role icons in sidebar</span>
<input type="checkbox" id="role-icon-sidebar" checked>
</label>
<label class="toggle-row">
<span>Show role icons in chat</span>
<input type="checkbox" id="role-icon-chat">
</label>
<label class="toggle-row">
<span>Show role icons after username</span>
<input type="checkbox" id="role-icon-after-name">
</label>
<small class="settings-hint">Controls where role icons appear. "After username" moves icons to the right side of the name instead of the left.</small>
</div>
<div id="roles-list-preview" style="margin-top:8px;">
</div>
</div>
<!-- Tunnel settings (Admin only) -->
<div class="admin-settings" id="section-tunnel" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🧭 <span data-i18n="settings.admin.tunnel_title">Tunnel</span></h5>
<label class="select-row">
<span data-i18n="settings.admin.tunnel_provider">Provider</span>
<select id="tunnel-provider-select">
<option value="localtunnel">LocalTunnel</option>
<option value="cloudflared">Cloudflared</option>
</select>
</label>
<div style="margin-top:8px;display:flex;align-items:center;gap:8px;">
<button class="btn-sm btn-accent" id="tunnel-toggle-btn" data-i18n="settings.admin.tunnel_start_btn">Start Tunnel</button>
<span id="tunnel-status-display" class="muted-text" data-i18n="settings.admin.tunnel_inactive" style="font-size:11px;">Inactive</span>
</div>
</div>
<!-- Webhooks / Bots (Admin only) -->
<div class="admin-settings" id="section-bots" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🤖 <span data-i18n="settings.admin.bots_title">Webhooks / Bots</span></h5>
<small class="settings-hint" data-i18n="settings.admin.bots_hint">Create and manage webhook bots that can post messages to channels.</small>
<div style="margin-top:8px;">
<button class="btn-sm btn-full btn-accent" id="open-bot-editor-btn">⚙️ <span data-i18n="settings.admin.manage_bots_btn">Manage Bots</span></button>
</div>
<div id="webhooks-list" style="margin-top:8px;">
<p class="muted-text" data-i18n="settings.admin.no_bots">No bots configured</p>
</div>
</div>
<!-- Custom Terms of Service (Admin only) -->
<div class="admin-settings" id="section-custom-tos" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">⚖️ <span data-i18n="settings.admin.custom_tos_title">Custom Terms of Service</span></h5>
<small class="settings-hint" data-i18n="settings.admin.custom_tos_hint">Add custom terms that appear before the default ToS on the login page. Supports plain text. Leave empty to show only the default Haven ToS.</small>
<textarea id="custom-tos-input" class="settings-textarea" rows="6" maxlength="50000" placeholder="Enter your custom terms of service..." style="margin-top:8px;width:100%;resize:vertical;min-height:80px;font-size:12px;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-family:inherit;"></textarea>
</div>
<!-- Discord Import (Admin only) -->
<div class="admin-settings" id="section-import" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">📦 <span data-i18n="settings.admin.import_title">Discord Import</span></h5>
<small class="settings-hint" data-i18n="settings.admin.import_hint">Import channels and messages from a Discord export file</small>
<div style="margin-top:8px;">
<button class="btn-sm btn-full btn-accent" id="open-import-btn">📦 <span data-i18n="settings.admin.import_btn">Import from Discord</span></button>
</div>
</div>
<!-- Mod Mode (Admin only) -->
<div class="admin-settings" id="section-modmode" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
<h5 class="settings-section-subtitle">🔧 <span data-i18n="settings.admin.modmode_title">Mod Mode</span></h5>
<small class="settings-hint" data-i18n="settings.admin.modmode_hint">Rearrange sidebar sections and snap server/sidebar/status panels</small>
<button class="btn-sm btn-full" id="mod-mode-settings-toggle" style="margin-top:6px;">🔧 <span data-i18n="settings.admin.modmode_toggle_btn">Toggle Mod Mode</span></button>
<button class="btn-sm btn-full" id="mod-mode-reset" data-i18n="settings.admin.modmode_reset_btn" style="margin-top:6px;">↺ Reset Layout</button>
</div>
</div>
</div><!-- /settings-body-admin -->
</div><!-- /settings-layout -->
<!-- Admin save bar — changes only apply when Save is clicked -->
<div class="admin-save-bar" style="display:none">
<button class="btn-accent btn-admin-save" id="admin-save-btn">💾 <span data-i18n="settings.admin.save_btn">Save Settings</span></button>
<small class="settings-hint" style="text-align:center;margin-top:4px" data-i18n="settings.admin.save_hint">Changes only apply when you click Save. Close (✕) to cancel.</small>
</div>
</div>
</div>
<!-- Discord Import Modal -->
<div class="modal-overlay" id="import-modal" style="display:none">
<div class="modal" style="width:560px">
<h3>📦 <span data-i18n="modals.discord_import.title">Import from Discord</span></h3>
<!-- Step 1: Upload or Connect -->
<div id="import-step-upload">
<div class="import-tabs">
<button class="import-tab active" data-tab="file">📁 <span data-i18n="modals.discord_import.file_tab">Upload File</span></button>
<button class="import-tab" data-tab="connect">🔗 <span data-i18n="modals.discord_import.connect_tab">Connect to Discord</span></button>
</div>
<!-- Tab: Upload File -->
<div id="import-tab-file">
<p class="settings-hint" style="margin-bottom:10px" data-i18n="modals.discord_import.upload_hint">
Upload a Discord export file to import channels and messages into Haven.
</p>
<div class="import-format-info">
<div class="import-format-row">
<strong>DiscordChatExporter</strong>
<span class="muted-text" data-i18n="modals.discord_import.format_dce">— JSON or ZIP (recommended — includes all users)</span>
</div>
<div class="import-format-row">
<strong>Discord Data Package</strong>
<span class="muted-text" data-i18n="modals.discord_import.format_ddp">— ZIP from Settings → Privacy (your messages only)</span>
</div>
</div>
<div class="import-dropzone" id="import-dropzone">
<input type="file" id="import-file-input" accept=".json,.zip" style="display:none">
<div class="import-dropzone-content">
<span class="import-dropzone-icon">📁</span>
<span><span data-i18n="modals.discord_import.drop_text">Drop file here or </span><a href="#" id="import-browse-link" data-i18n="modals.discord_import.browse_link">browse</a></span>
<span class="muted-text" style="font-size:11px" data-i18n="modals.discord_import.dropzone_hint">.json or .zip — up to 500 MB</span>
</div>
</div>
<div id="import-upload-progress" style="display:none">
<div class="import-progress-bar"><div class="import-progress-fill" id="import-progress-fill"></div></div>
<p class="muted-text" id="import-upload-status" style="margin-top:6px;text-align:center" data-i18n="modals.discord_import.uploading">Uploading...</p>
</div>
</div>
<!-- Tab: Connect to Discord -->
<div id="import-tab-connect" style="display:none">
<div id="import-connect-step-token">
<p class="settings-hint" style="margin-bottom:8px" data-i18n="modals.discord_import.connect_hint">
Connect directly to Discord and pull your messages — no external tools needed.
</p>
<details class="import-token-help">
<summary data-i18n="modals.discord_import.token_help_summary">How do I get my Discord token?</summary>
<div class="import-token-steps">
<ol>
<li data-i18n-html="modals.discord_import.token_step_1">Open <a href="https://discord.com/app" target="_blank" rel="noopener">discord.com/app</a> in Chrome/Edge (or use the desktop app)</li>
<li data-i18n-html="modals.discord_import.token_step_2">Press <kbd>F12</kbd> to open DevTools</li>
<li data-i18n-html="modals.discord_import.token_step_3">Click the <strong>Application</strong> tab (click <code>&gt;&gt;</code> in the toolbar if you don't see it)</li>
<li data-i18n-html="modals.discord_import.token_step_4">In the left sidebar, expand <strong>Local Storage</strong> → click <strong>https://discord.com</strong></li>
<li data-i18n-html="modals.discord_import.token_step_5">Find the <code>token</code> key — copy the value (without the quotes)</li>
</ol>
<p style="margin-top:8px" data-i18n-html="modals.discord_import.desktop_app_hint"><strong>Desktop app:</strong> If F12 doesn't open DevTools, close Discord fully, then edit:</p>
<code class="import-token-snippet">%appdata%\discord\settings.json</code>
<p style="margin-top:4px" data-i18n="modals.discord_import.add_line_hint">Add this line anywhere inside the { }:</p>
<code class="import-token-snippet">"DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true</code>
<p style="margin-top:4px" data-i18n="modals.discord_import.restart_hint">Restart Discord, then follow the steps above.</p>
<p class="muted-text" style="margin-top:8px">⚠️ <span data-i18n="modals.discord_import.token_warning">Your token is like a password. Haven uses it only for this import and never stores it.</span></p>
</div>
</details>
<div style="display:flex;gap:8px;margin-top:10px">
<input type="password" id="import-discord-token" class="import-token-input" data-i18n-placeholder="modals.discord_import.token_placeholder" placeholder="Paste your Discord token here">
<button class="btn-sm btn-accent" id="import-connect-btn" data-i18n="modals.discord_import.connect_btn">Connect</button>
</div>
<p class="muted-text" id="import-connect-status" style="margin-top:6px;display:none"></p>
</div>
<div id="import-connect-step-servers" style="display:none">
<div class="import-info-bar" style="margin-bottom:8px">
<span><span data-i18n="modals.discord_import.connected_as">Connected as</span> <strong id="import-discord-username"></strong></span>
<a href="#" id="import-connect-disconnect" data-i18n="modals.discord_import.disconnect" style="font-size:11px;margin-left:auto">Disconnect</a>
</div>
<p class="settings-hint" style="margin-bottom:6px" data-i18n="modals.discord_import.pick_server">Pick a server to import from:</p>
<div id="import-server-list" class="import-server-grid"></div>
</div>
<div id="import-connect-step-channels" style="display:none">
<div class="import-info-bar" style="margin-bottom:8px">
<span id="import-connect-guild-name" style="font-weight:600"></span>
<a href="#" id="import-connect-back-servers" data-i18n="modals.discord_import.back_to_servers" style="font-size:11px;margin-left:auto">← Back to servers</a>
</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
<p class="settings-hint" style="margin:0;flex:1" data-i18n="modals.discord_import.select_channels">Select channels to fetch:</p>
<a href="#" id="import-connect-toggle-all" data-i18n="modals.discord_import.deselect_all" style="font-size:11px;white-space:nowrap">Deselect All</a>
</div>
<div class="import-channel-list" id="import-connect-channel-list"></div>
<div style="display:flex;gap:8px;margin-top:10px;justify-content:flex-end">
<button class="btn-sm btn-accent" id="import-fetch-btn">📥 <span data-i18n="modals.discord_import.fetch_btn">Fetch Messages</span></button>
</div>
<p class="muted-text" id="import-fetch-status" style="margin-top:6px;display:none"></p>
</div>
</div>
</div>
<!-- Step 2: Preview & Select -->
<div id="import-step-preview" style="display:none">
<div class="import-info-bar">
<span class="import-format-badge" id="import-format-badge"></span>
<span id="import-server-name" style="font-weight:600"></span>
<span class="muted-text" id="import-total-msgs"></span>
</div>
<div style="display:flex;align-items:center;gap:8px;margin:8px 0 6px">
<p class="settings-hint" style="margin:0;flex:1" data-i18n="modals.discord_import.preview_select_hint">Select channels to import. You can rename them before importing.</p>
<a href="#" id="import-toggle-all" data-i18n="modals.discord_import.deselect_all" style="font-size:11px;white-space:nowrap">Deselect All</a>
</div>
<div class="import-channel-list" id="import-channel-list"></div>
<div style="display:flex;gap:8px;margin-top:12px;justify-content:flex-end">
<button class="btn-sm" id="import-back-btn" data-i18n="modals.common.back">← Back</button>
<button class="btn-sm btn-accent" id="import-execute-btn">📦 <span data-i18n="modals.discord_import.import_selected_btn">Import Selected</span></button>
</div>
</div>
<!-- Step 3: Done -->
<div id="import-step-done" style="display:none;text-align:center;padding:20px 0">
<div style="font-size:48px;margin-bottom:12px"></div>
<p id="import-done-msg" style="font-size:14px" data-i18n="modals.discord_import.import_complete">Import complete!</p>
</div>
<div class="modal-actions" style="margin-top:12px">
<button class="btn-sm" id="close-import-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Role Management Modal -->
<div class="modal-overlay" id="role-modal" style="display:none">
<div class="modal modal-wide">
<h3>👑 <span data-i18n="modals.role_management.title">Role Management</span></h3>
<div class="role-editor-layout">
<div class="role-sidebar">
<div id="role-list-sidebar" class="role-list-sidebar"></div>
<button class="btn-sm btn-accent btn-full" id="create-role-btn" data-i18n="modals.role_management.new_role_btn" style="margin-top:8px;">+ New Role</button>
</div>
<div class="role-detail" id="role-detail-panel">
<p class="muted-text" style="padding:20px;text-align:center" data-i18n="modals.role_management.select_to_edit">Select a role to edit</p>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm btn-accent" id="save-role-btn" data-i18n="modals.common.save" style="display:none">Save</button>
<button class="btn-sm" id="close-role-modal-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Assign Role Modal -->
<div class="modal-overlay" id="assign-role-modal" style="display:none">
<div class="modal" style="width:440px">
<h3>👑 <span data-i18n="modals.assign_role.title">Assign Role</span></h3>
<p id="assign-role-user-label" class="muted-text"></p>
<label class="assign-role-field-label" data-i18n="modals.assign_role.role_label">Role</label>
<select id="assign-role-select" class="settings-select assign-role-select">
<option value="" data-i18n="modals.assign_role.select_role">-- Select Role --</option>
</select>
<label class="assign-role-field-label" style="margin-top:10px" data-i18n="modals.assign_role.scope_label">Scope</label>
<p class="assign-role-hint" data-i18n="modals.assign_role.scope_hint">Server-wide applies to all channels. Channel-specific only applies within that channel.</p>
<select id="assign-role-scope" class="settings-select assign-role-select">
<option value="server" data-i18n="modals.assign_role.server_wide">Server-wide</option>
</select>
<div class="modal-actions">
<button class="btn-sm" id="cancel-assign-role-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="confirm-assign-role-btn" data-i18n="modals.assign_role.assign_btn">Assign</button>
</div>
</div>
</div>
<!-- Centralized Role Assignment (3-pane) Modal -->
<div class="modal-overlay" id="role-assign-center-modal" style="display:none">
<div class="modal role-assign-center-box">
<h3>👑 <span data-i18n="modals.role_assign_center.title">Role Assignment</span></h3>
<p class="muted-text role-assign-center-subtitle" data-i18n="modals.role_assign_center.subtitle">Select a user, then a channel, then configure their role.</p>
<div class="rac-layout">
<!-- Left pane: Users -->
<div class="rac-pane rac-users-pane">
<div class="rac-pane-header">
<span class="rac-pane-title" data-i18n="modals.role_assign_center.users_pane">Users</span>
<input type="text" id="rac-user-search" class="rac-search" data-i18n-placeholder="modals.role_assign_center.search_users_placeholder" placeholder="Search users…">
</div>
<div class="rac-pane-list" id="rac-user-list"></div>
</div>
<!-- Center pane: Channels -->
<div class="rac-pane rac-channels-pane">
<div class="rac-pane-header">
<span class="rac-pane-title" data-i18n="modals.role_assign_center.channels_pane">Channels</span>
</div>
<div class="rac-pane-list" id="rac-channel-list">
<p class="rac-placeholder" data-i18n="modals.role_assign_center.select_user_hint">← Select a user</p>
</div>
</div>
<!-- Right pane: Role config -->
<div class="rac-pane rac-config-pane">
<div class="rac-pane-header">
<span class="rac-pane-title" data-i18n="modals.role_assign_center.config_pane">Role Configuration</span>
</div>
<div class="rac-config-body" id="rac-config-body">
<p class="rac-placeholder" data-i18n="modals.role_assign_center.select_channel_hint">← Select a channel</p>
</div>
</div>
</div>
<div class="modal-actions rac-footer">
<button class="btn-sm" id="rac-manage-roles-btn" style="display:none" data-i18n-title="modals.role_assign_center.manage_roles_title" title="Open role management">⚙️ <span data-i18n="modals.role_assign_center.manage_roles_btn">Manage Roles</span></button>
<div class="rac-footer-right">
<button class="btn-sm" id="rac-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="rac-save-btn" data-i18n="modals.role_assign_center.save_btn" disabled>Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Bot Management Modal -->
<div class="modal-overlay" id="bot-modal" style="display:none">
<div class="modal modal-wide">
<h3>🤖 <span data-i18n="modals.bot_mgmt.title">Bot Management</span></h3>
<div class="role-editor-layout">
<div class="role-sidebar">
<div id="bot-list-sidebar" class="role-list-sidebar"></div>
<button class="btn-sm btn-accent btn-full" id="create-bot-btn" data-i18n="modals.bot_mgmt.new_bot_btn" style="margin-top:8px;">+ New Bot</button>
</div>
<div class="role-detail" id="bot-detail-panel">
<p class="muted-text" style="padding:20px;text-align:center" data-i18n="modals.bot_mgmt.select_or_create">Select a bot to edit, or create a new one</p>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-bot-modal-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Sound Management Modal (Full Popout) -->
<div class="modal-overlay" id="sound-modal" style="display:none">
<div class="modal" style="width:640px;min-height:420px">
<h3>🔊 <span data-i18n="modals.sound_manager.title">Sound Manager</span></h3>
<!-- Tabs -->
<div class="sound-modal-tabs">
<button class="sound-tab active" data-tab="soundboard">🎵 <span data-i18n="modals.sound_manager.soundboard_tab">Soundboard</span></button>
<button class="sound-tab" data-tab="assign">🔔 <span data-i18n="modals.sound_manager.assign_tab">Assign to Events</span></button>
<button class="sound-tab sound-tab-admin" data-tab="manage" style="display:none">⚙️ <span data-i18n="modals.sound_manager.manage_tab">Manage</span></button>
</div>
<!-- TAB: Soundboard (all users) -->
<div class="sound-tab-content active" id="sound-tab-soundboard">
<small class="settings-hint" style="display:block;margin-bottom:8px" data-i18n="modals.sound_manager.modal_hint">Browse & play custom sounds. Set hotkeys to play them anytime.</small>
<div class="sound-search-row">
<input type="text" id="soundboard-search" data-i18n-placeholder="modals.sound_manager.search_placeholder" placeholder="Search sounds..." class="settings-text-input" style="flex:1">
<button id="soundboard-popout-btn" class="icon-btn small" title="Pop out soundboard"></button>
</div>
<div id="soundboard-grid" class="soundboard-grid">
<p class="muted-text" data-i18n="modals.sound_manager.no_sounds">No sounds available</p>
</div>
</div>
<!-- TAB: Assign to Events (all users) -->
<div class="sound-tab-content" id="sound-tab-assign">
<small class="settings-hint" style="display:block;margin-bottom:10px" data-i18n="modals.sound_manager.assign_hint">Choose which sound plays for each event. Custom sounds uploaded by the admin appear here too.</small>
<div class="sound-assign-list">
<label class="select-row">
<span>📨 <span data-i18n="modals.sound_manager.new_message">New Message</span></span>
<select id="assign-msg-sound" class="sound-assign-select"></select>
</label>
<label class="select-row">
<span>📤 <span data-i18n="modals.sound_manager.msg_sent">Message Sent</span></span>
<select id="assign-sent-sound" class="sound-assign-select"></select>
</label>
<label class="select-row">
<span>📢 <span data-i18n="modals.sound_manager.mention">@Mention</span></span>
<select id="assign-mention-sound" class="sound-assign-select"></select>
</label>
<label class="select-row">
<span>👋 <span data-i18n="modals.sound_manager.user_joined">User Joined</span></span>
<select id="assign-join-sound" class="sound-assign-select"></select>
</label>
<label class="select-row">
<span>🚪 <span data-i18n="modals.sound_manager.user_left">User Left</span></span>
<select id="assign-leave-sound" class="sound-assign-select"></select>
</label>
</div>
</div>
<!-- TAB: Manage (admin only) -->
<div class="sound-tab-content" id="sound-tab-manage">
<small class="settings-hint" style="display:block;margin-bottom:12px" data-i18n="modals.sound_manager.upload_hint">Upload audio files for custom sounds (max 1 MB each, mp3/ogg/wav/webm)</small>
<div class="whitelist-add-row">
<input type="text" id="sound-name-input" data-i18n-placeholder="modals.sound_manager.name_placeholder" 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" data-i18n="modals.sound_manager.upload_btn">Upload</button>
</div>
<div id="custom-sounds-list" style="margin-top:12px;">
<p class="muted-text" data-i18n="modals.sound_manager.no_custom_sounds">No custom sounds uploaded</p>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-sound-modal-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Emoji Management Modal -->
<div class="modal-overlay" id="emoji-modal" style="display:none">
<div class="modal" style="width:520px">
<h3>😎 <span data-i18n="modals.emoji_mgmt.title">Emoji Management</span></h3>
<small class="settings-hint" style="display:block;margin-bottom:12px" data-i18n="settings.admin.emojis_upload_hint">Upload images for custom server emojis (max 256 KB, png/gif/webp). Name must be lowercase, no spaces.</small>
<div class="whitelist-add-row">
<input type="text" id="emoji-name-input" data-i18n-placeholder="modals.emoji_mgmt.name_placeholder" placeholder="emoji_name" maxlength="30" class="settings-text-input">
<input type="file" id="emoji-file-input" accept="image/png,image/gif,image/webp,image/jpeg" style="max-width:120px;font-size:11px">
<button class="btn-sm btn-accent" id="emoji-upload-btn" data-i18n="modals.emoji_mgmt.upload_btn">Upload</button>
</div>
<div style="margin-top:6px;text-align:center">
<label class="btn-sm" style="cursor:pointer;display:inline-block" title="Select multiple images — names auto-generated from filenames">
📦 Bulk Upload
<input type="file" id="emoji-bulk-input" accept="image/png,image/gif,image/webp,image/jpeg" multiple style="display:none">
</label>
</div>
<div id="emoji-crop-preview-row" style="display:none;align-items:center;gap:10px;margin-top:8px;padding:8px;background:var(--bg-tertiary);border-radius:var(--radius-sm)">
<img id="emoji-crop-thumb" src="" alt="" style="width:32px;height:32px;border-radius:4px;object-fit:cover;border:1px solid var(--border)">
<span class="settings-hint" style="flex:1" data-i18n="modals.emoji_mgmt.crop_applied">Crop applied — ready to upload</span>
<button class="btn-xs" id="emoji-recrop-btn" title="Re-open cropper">✏️ <span data-i18n="modals.emoji_mgmt.adjust_btn">Adjust</span></button>
</div>
<div id="custom-emojis-list" style="margin-top:12px;">
<p class="muted-text" data-i18n="modals.emoji_mgmt.no_emojis">No custom emojis uploaded</p>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-emoji-modal-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Emoji Crop Modal -->
<div class="modal-overlay" id="emoji-crop-modal" style="display:none">
<div class="modal" style="width:320px;text-align:center">
<h3 style="margin-bottom:6px">✂️ <span data-i18n="modals.emoji_crop.title">Crop Emoji</span></h3>
<small class="settings-hint" style="display:block;margin-bottom:10px" data-i18n="modals.emoji_crop.hint">Drag to reposition · Slider to zoom</small>
<canvas id="emoji-crop-canvas" width="256" height="256" style="border-radius:var(--radius);cursor:grab;display:block;margin:0 auto 10px;border:2px solid var(--accent);max-width:100%;touch-action:none"></canvas>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;padding:0 4px">
<span style="font-size:13px" title="Zoom out">🔍</span>
<input type="range" id="emoji-crop-zoom" min="100" max="500" value="100" step="1" style="flex:1;accent-color:var(--accent)">
<span style="font-size:13px" title="Zoom in">🔎</span>
</div>
<div class="modal-actions">
<button class="btn-sm" id="emoji-crop-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="emoji-crop-confirm-btn" data-i18n="modals.emoji_crop.confirm_btn">Use This Crop</button>
</div>
</div>
</div>
<div class="modal-overlay" id="bans-modal" style="display:none">
<div class="modal modal-wide">
<h3 data-i18n="modals.bans.title">Banned Users</h3>
<div id="bans-list" class="bans-list">
<p class="muted-text" data-i18n="modals.common.loading">Loading...</p>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-bans-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<div class="modal-overlay" id="deleted-users-modal" style="display:none">
<div class="modal modal-wide">
<h3>🗑️ <span data-i18n="modals.deleted_users.title">Deleted Users</span></h3>
<div id="deleted-users-list" class="bans-list">
<p class="muted-text" data-i18n="modals.common.loading">Loading...</p>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-deleted-users-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- All Members Modal -->
<div class="modal-overlay" id="all-members-modal" style="display:none">
<div class="modal modal-wide" style="max-width:720px;max-height:85vh;display:flex;flex-direction:column">
<h3>👥 <span data-i18n="modals.all_members.title">All Members</span> <span id="all-members-count" class="muted-text" style="font-size:13px;font-weight:normal"></span></h3>
<div style="margin-bottom:10px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input type="text" id="all-members-search" data-i18n-placeholder="modals.all_members.search_placeholder" placeholder="Search users..." maxlength="40" class="settings-text-input" style="flex:1;min-width:140px">
<select id="all-members-filter" style="font-size:12px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border)">
<option value="all" data-i18n="modals.all_members.filter_all">All</option>
<option value="online" data-i18n="modals.all_members.filter_online">Online</option>
<option value="offline" data-i18n="modals.all_members.filter_offline">Offline</option>
<option value="new" data-i18n="modals.all_members.filter_new">New (7 days)</option>
<option value="banned" data-i18n="modals.all_members.filter_banned">Banned</option>
</select>
</div>
<div id="all-members-list" style="overflow-y:auto;flex:1;min-height:0;display:flex;flex-direction:column;gap:4px">
<p class="muted-text" style="text-align:center;padding:20px" data-i18n="modals.common.loading">Loading...</p>
</div>
<div class="modal-actions">
<button class="btn-sm" id="close-all-members-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Music Share Modal -->
<div class="modal-overlay" id="music-modal" style="display:none">
<div class="modal">
<h3>🎵 <span data-i18n="modals.listen_together.title">Listen Together</span></h3>
<p class="modal-desc" data-i18n="modals.listen_together.desc">Paste a link from Spotify, YouTube, or SoundCloud to share with everyone in voice</p>
<div class="form-group">
<label data-i18n="modals.listen_together.link_label">Music Link</label>
<input type="text" id="music-link-input" data-i18n-placeholder="modals.listen_together.link_placeholder" 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" data-i18n="modals.common.cancel">Cancel</button>
<div style="display:flex;flex-direction:column;gap:6px;align-items:stretch">
<button class="btn-sm btn-accent" id="share-music-btn" data-i18n="modals.listen_together.share_btn">Share with Channel</button>
<button class="btn-sm btn-accent" id="share-music-playlist-btn" style="display:none" data-i18n="media.share_playlist">Share Playlist with Channel</button>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="music-queue-modal" style="display:none">
<div class="modal modal-wide music-queue-modal">
<h3>📋 <span data-i18n="media.music_queue_title">Playback Queue</span></h3>
<p class="modal-desc" id="music-queue-summary" data-i18n="media.music_queue_empty_summary">No queued tracks</p>
<div class="music-queue-table-wrap">
<table class="music-queue-table">
<thead>
<tr>
<th class="music-queue-col-handle"></th>
<th class="music-queue-col-pos" data-i18n="media.music_queue_col_pos">Pos.</th>
<th class="music-queue-col-requested-by" data-i18n="media.music_queue_col_requested_by">Requested By</th>
<th class="music-queue-col-title" data-i18n="media.music_queue_col_title">Title</th>
<th class="music-queue-col-actions"></th>
</tr>
</thead>
<tbody id="music-queue-body">
<tr><td colspan="5" class="music-queue-empty" data-i18n="media.music_queue_is_empty">Queue is empty</td></tr>
</tbody>
</table>
</div>
<div class="modal-actions">
<button class="btn-sm btn-accent" id="shuffle-music-queue-btn" style="display:none" data-i18n="media.music_shuffle_queue">Shuffle Queue</button>
<button class="btn-sm" id="close-music-queue-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Manage Servers Modal -->
<div class="modal-overlay" id="manage-servers-modal" style="display:none">
<div class="modal manage-servers-modal-inner">
<h3><span data-i18n="modals.manage_servers.title">Manage Servers</span></h3>
<p class="modal-desc" data-i18n="modals.manage_servers.desc">Edit or remove your linked servers</p>
<div id="manage-servers-list" class="manage-servers-list"></div>
<div class="modal-actions" style="justify-content:space-between">
<button class="btn-sm btn-accent" id="manage-servers-add-btn" data-i18n="modals.manage_servers.add_btn">+ Add Server</button>
<button class="btn-sm" id="manage-servers-close-btn" data-i18n="modals.common.close">Close</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="mark-read"><span data-i18n="context_menu.channel.mark_read">Mark as Read</span></button>
<button class="channel-ctx-item" data-action="mute">🔔 <span data-i18n="context_menu.channel.mute">Mute Channel</span></button>
<button class="channel-ctx-item" data-action="join-voice">🎙️ <span data-i18n="voice.join_ctx">Join Voice</span></button>
<button class="channel-ctx-item" data-action="leave-voice" style="display:none">📴 <span data-i18n="context_menu.channel.disconnect_voice">Disconnect Voice</span></button>
<hr class="channel-ctx-sep">
<button class="channel-ctx-item" data-action="leave-channel">🚪 <span data-i18n="context_menu.channel.leave">Leave Channel</span></button>
<hr class="channel-ctx-sep">
<button class="channel-ctx-item mod-only" data-action="rename-channel">✏️ <span data-i18n="context_menu.channel.rename">Rename Channel</span></button>
<button class="channel-ctx-item mod-only" data-action="create-sub-channel">📁 <span data-i18n="context_menu.channel.create_sub">Create Sub-channel</span></button>
<hr class="channel-ctx-sep admin-only">
<button class="channel-ctx-item admin-only" data-action="organize" style="display:none">📋 <span data-i18n="context_menu.channel.organize">Organize</span></button>
<button class="channel-ctx-item admin-only" data-action="move-to-parent" style="display:none">📦 <span data-i18n="context_menu.channel.move_to">Move to…</span></button>
<button class="channel-ctx-item admin-only" data-action="promote-channel" style="display:none">⬆️ <span data-i18n="context_menu.channel.promote">Promote to Channel</span></button>
<button class="channel-ctx-item admin-only" data-action="channel-functions">⚙️ <span data-i18n="context_menu.channel.functions">Channel Functions</span> <span class="cfn-arrow"></span></button>
<div id="channel-functions-panel" class="channel-functions-panel" style="display:none">
<div class="cfn-row" data-fn="voice" data-i18n-title="channel_functions.voice_hint" title="Enable or disable voice chat in this channel">
<span class="cfn-label">🎙️ <span data-i18n="channel_functions.voice">Voice</span></span>
<span class="cfn-badge cfn-on" data-i18n="channel_functions.on">ON</span>
</div>
<div class="cfn-row" data-fn="text" data-i18n-title="channel_functions.text_hint" title="Enable or disable text chat in this channel">
<span class="cfn-label">💬 <span data-i18n="channel_functions.text">Text</span></span>
<span class="cfn-badge cfn-on" data-i18n="channel_functions.on">ON</span>
</div>
<div class="cfn-row" data-fn="streams" data-i18n-title="channel_functions.streams_hint" title="Allow screen sharing and camera (requires voice)">
<span class="cfn-label">🖥️ <span data-i18n="channel_functions.streams">Streams</span></span>
<span class="cfn-badge cfn-on" data-i18n="channel_functions.on">ON</span>
</div>
<div class="cfn-row" data-fn="music" data-i18n-title="channel_functions.music_hint" title="Allow Listen Together / music playback (requires voice)">
<span class="cfn-label">🎵 <span data-i18n="channel_functions.music">Music</span></span>
<span class="cfn-badge cfn-on" data-i18n="channel_functions.on">ON</span>
</div>
<div class="cfn-row" data-fn="media" data-i18n-title="channel_functions.media_hint" title="Allow image, video, and file uploads in this channel">
<span class="cfn-label">🖼️ <span data-i18n="channel_functions.media">Media</span></span>
<span class="cfn-badge cfn-on" data-i18n="channel_functions.on">ON</span>
</div>
<div class="cfn-row" data-fn="read-only" data-i18n-title="channel_functions.read_only_hint" title="Make this channel read-only — only users with the Read-Only Override permission can post">
<span class="cfn-label">🔒 <span data-i18n="channel_functions.read_only">Read Only</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-divider"></div>
<div class="cfn-row" data-fn="slow-mode" data-i18n-title="channel_functions.slow_mode_hint" title="Require a cooldown between messages (0 = off)">
<span class="cfn-label">🐢 <span data-i18n="channel_functions.slow_mode">Slow Mode</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-row" data-fn="cleanup-exempt" data-i18n-title="channel_functions.cleanup_guard_hint" title="Protect this channel from automatic message cleanup">
<span class="cfn-label">🛡️ <span data-i18n="channel_functions.cleanup_guard">Cleanup Guard</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-row" data-fn="user-limit" data-i18n-title="channel_functions.voice_limit_hint" title="Limit simultaneous voice users (click to set; blank = unlimited)">
<span class="cfn-label">👥 <span data-i18n="channel_functions.voice_limit">Voice Limit</span></span>
<span class="cfn-badge cfn-off"></span>
</div>
<div class="cfn-row" data-fn="voice-bitrate" data-i18n-title="channel_functions.voice_bitrate_hint" title="Cap voice audio bitrate in kbps (0 = auto / no cap)">
<span class="cfn-label">🎚️ <span data-i18n="channel_functions.voice_bitrate">Voice Bitrate</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.voice_bitrate_auto">Auto</span>
</div>
<div class="cfn-divider"></div>
<div class="cfn-row" data-fn="self-destruct" data-i18n-title="channel_functions.self_destruct_hint" title="Set a self-destruct timer — channel auto-deletes after the specified hours (0 = off)">
<span class="cfn-label">⏱️ <span data-i18n="channel_functions.self_destruct">Self Destruct</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-row" data-fn="announcement" data-i18n-title="channel_functions.announcement_hint" title="Announcement channel — messages use a distinct notification sound and gold highlight">
<span class="cfn-label">📢 <span data-i18n="channel_functions.announcement">Announcement</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-divider cfn-afk-divider" style="display:none"></div>
<div class="cfn-row cfn-afk-row" data-fn="afk-sub" style="display:none" data-i18n-title="channel_functions.afk_sub_hint" title="Designate a sub-channel as the AFK voice room — idle users are moved here">
<span class="cfn-label">💤 <span data-i18n="channel_functions.afk_sub">AFK Sub</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
<div class="cfn-row cfn-afk-row" data-fn="afk-timeout" style="display:none" data-i18n-title="channel_functions.afk_timeout_hint" title="Minutes of voice inactivity before moving to AFK sub (0 = disabled)">
<span class="cfn-label">⏱️ <span data-i18n="channel_functions.afk_timeout">AFK Timeout</span></span>
<span class="cfn-badge cfn-off" data-i18n="channel_functions.off">OFF</span>
</div>
</div>
<button class="channel-ctx-item admin-only" data-action="channel-roles">👑 <span data-i18n="context_menu.channel.roles">Roles</span></button>
<button class="channel-ctx-item admin-only" data-action="webhooks">🤖 <span data-i18n="context_menu.channel.webhooks">Webhooks</span></button>
<hr class="channel-ctx-sep admin-only">
<button class="channel-ctx-item admin-only danger" data-action="delete">🗑️ <span data-i18n="context_menu.channel.delete">Delete Channel</span></button>
</div>
<!-- DM context menu -->
<div id="dm-ctx-menu" class="channel-ctx-menu" style="display:none">
<button class="channel-ctx-item" data-action="dm-mark-read"><span data-i18n="context_menu.dm.mark_read">Mark as Read</span></button>
<button class="channel-ctx-item" data-action="dm-mute">🔔 <span data-i18n="context_menu.dm.mute">Mute DM</span></button>
<hr class="channel-ctx-sep">
<button class="channel-ctx-item danger" data-action="dm-delete">🗑️ <span data-i18n="context_menu.dm.delete">Delete DM</span></button>
</div>
<!-- Organize Sub-channels Modal -->
<div class="modal-overlay" id="organize-modal" style="display:none">
<div class="modal" style="width:500px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
<button id="organize-back-btn" class="btn-sm" style="display:none;padding:3px 8px;font-size:0.8rem" data-i18n-title="modals.organize.back_title" title="Back to channel list"><span data-i18n="modals.organize.back_btn">← Back</span></button>
<h3 id="organize-modal-title" style="margin:0">📋 <span data-i18n="modals.organize.title">Organize</span></h3>
</div>
<p class="modal-desc" id="organize-modal-parent-name"></p>
<div class="organize-toolbar">
<label class="organize-toolbar-label" data-i18n="modals.organize.sort_label">Sort:</label>
<select id="organize-global-sort" class="organize-select">
<option value="server_default">Server Default</option>
<option value="manual" data-i18n="modals.organize.sort_manual">Manual</option>
<option value="alpha" data-i18n="modals.organize.sort_alpha">Alphabetical</option>
<option value="created" data-i18n="modals.organize.sort_newest">Newest First</option>
<option value="oldest" data-i18n="modals.organize.sort_oldest">Oldest First</option>
<option value="dynamic" data-i18n="modals.organize.sort_dynamic">Dynamic</option>
</select>
</div>
<div class="organize-toolbar" id="organize-cat-toolbar" style="display:none">
<label class="organize-toolbar-label" data-i18n="modals.organize.categories_label">Categories:</label>
<select id="organize-cat-sort" class="organize-select">
<option value="manual" data-i18n="modals.organize.cat_manual">Manual Order</option>
<option value="az" data-i18n="modals.organize.cat_az">A → Z</option>
<option value="za" data-i18n="modals.organize.cat_za">Z → A</option>
</select>
</div>
<div id="organize-channel-list" class="organize-list"></div>
<div class="organize-controls">
<button class="btn-sm" id="organize-move-up" data-i18n="modals.organize.move_up" data-i18n-title="modals.organize.move_up_title" title="Move selected channel up">⬆ Up</button>
<button class="btn-sm" id="organize-move-down" data-i18n="modals.organize.move_down" data-i18n-title="modals.organize.move_down_title" title="Move selected channel down">⬇ Down</button>
<div class="organize-spacer"></div>
<div class="organize-tag-group">
<input type="text" id="organize-tag-input" class="organize-tag-input" maxlength="30" data-i18n-placeholder="modals.organize.tag_placeholder" placeholder="Tag name…">
<button class="btn-sm organize-tag-btn" id="organize-set-tag" data-i18n-title="modals.organize.tag_set_title" title="Apply tag">🏷️ <span data-i18n="modals.organize.tag_set_btn">Set</span></button>
<button class="btn-sm organize-tag-btn danger" id="organize-remove-tag" data-i18n="modals.organize.tag_remove_btn" data-i18n-title="modals.organize.tag_remove_title" title="Remove tag"></button>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm btn-accent" id="organize-done-btn" data-i18n="modals.common.done">Done</button>
</div>
</div>
</div>
<!-- Move Channel Picker Modal -->
<div class="modal-overlay" id="reparent-modal" style="display:none">
<div class="modal" style="max-width:380px">
<h3 id="reparent-modal-title">📦 <span data-i18n="modals.move_channel.title">Move Channel</span></h3>
<p class="modal-desc" id="reparent-modal-desc"></p>
<div id="reparent-channel-list" class="organize-list" style="max-height:300px;overflow-y:auto"></div>
<div class="modal-actions" style="display:flex;gap:8px;justify-content:flex-end;margin-top:12px">
<button class="btn-sm" id="reparent-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
</div>
</div>
</div>
<!-- DM Organize Modal -->
<div class="modal-overlay" id="dm-organize-modal" style="display:none">
<div class="modal" style="width:460px">
<h3>📋 <span data-i18n="modals.dm_organize.title">Organize DMs</span></h3>
<p class="modal-desc" data-i18n="modals.dm_organize.desc">Tag your conversations into collapsible categories</p>
<div class="organize-toolbar">
<label class="organize-toolbar-label" data-i18n="modals.dm_organize.sort_label">Sort:</label>
<select id="dm-organize-sort" class="organize-select">
<option value="manual" data-i18n="modals.dm_organize.sort_manual">Manual</option>
<option value="alpha" data-i18n="modals.dm_organize.sort_alpha">Alphabetical</option>
<option value="recent" data-i18n="modals.dm_organize.sort_recent">Recent Activity</option>
</select>
</div>
<div id="dm-organize-list" class="organize-list"></div>
<div class="organize-controls">
<button class="btn-sm" id="dm-organize-move-up" data-i18n="modals.organize.move_up" data-i18n-title="modals.organize.move_up_title" title="Move up">⬆ Up</button>
<button class="btn-sm" id="dm-organize-move-down" data-i18n="modals.organize.move_down" data-i18n-title="modals.organize.move_down_title" title="Move down">⬇ Down</button>
<div class="organize-spacer"></div>
<div class="organize-tag-group">
<input type="text" id="dm-organize-tag-input" class="organize-tag-input" maxlength="30" data-i18n-placeholder="modals.dm_organize.category_placeholder" placeholder="Category…">
<button class="btn-sm organize-tag-btn" id="dm-organize-set-tag" data-i18n-title="modals.dm_organize.category_set_title" title="Apply category">🏷️ <span data-i18n="modals.organize.tag_set_btn">Set</span></button>
<button class="btn-sm organize-tag-btn danger" id="dm-organize-remove-tag" data-i18n="modals.organize.tag_remove_btn" data-i18n-title="modals.dm_organize.category_remove_title" title="Remove category"></button>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm btn-accent" id="dm-organize-done-btn" data-i18n="modals.common.done">Done</button>
</div>
</div>
</div>
<!-- Webhook Management Modal -->
<!-- Poll Creation Modal -->
<div class="modal-overlay" id="poll-modal" style="display:none">
<div class="modal" style="width:440px">
<h3>📊 <span data-i18n="modals.poll.title">Create Poll</span></h3>
<div class="form-group">
<label data-i18n="modals.poll.question_label">Question</label>
<input type="text" id="poll-question-input" data-i18n-placeholder="modals.poll.question_placeholder" placeholder="What do you want to ask?" maxlength="300" autocomplete="off">
</div>
<div class="form-group" style="margin-top:8px">
<label data-i18n="modals.poll.options_label">Options</label>
<div id="poll-options-list" class="poll-options-list">
<div class="poll-option-row"><input type="text" class="poll-option-input" placeholder="Option 1" maxlength="100"><button class="poll-option-remove" title="Remove" style="display:none">&times;</button></div>
<div class="poll-option-row"><input type="text" class="poll-option-input" placeholder="Option 2" maxlength="100"><button class="poll-option-remove" title="Remove" style="display:none">&times;</button></div>
</div>
<button class="btn-sm" id="poll-add-option-btn" style="margin-top:6px" data-i18n="modals.poll.add_option_btn">+ Add Option</button>
</div>
<div class="form-group poll-settings-row" style="margin-top:10px">
<label class="poll-checkbox-label"><input type="checkbox" id="poll-multi-vote"> <span data-i18n="modals.poll.multi_vote">Allow multiple votes</span></label>
<label class="poll-checkbox-label"><input type="checkbox" id="poll-anonymous"> <span data-i18n="modals.poll.anonymous">Anonymous votes</span></label>
</div>
<div class="modal-actions">
<button class="btn-sm" id="poll-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="poll-create-btn" data-i18n="modals.poll.create_btn">Create Poll</button>
</div>
</div>
</div>
<!-- Channel Roles Modal -->
<div class="modal-overlay" id="channel-roles-modal" style="display:none">
<div class="modal modal-wide">
<h3>👑 <span data-i18n="modals.channel_roles.title">Channel Roles</span></h3>
<p class="modal-desc" id="channel-roles-channel-name"></p>
<div class="channel-roles-layout">
<!-- Left: Members + Assignment -->
<div class="channel-roles-left">
<h4 class="channel-roles-section-title" data-i18n="modals.channel_roles.members_title">Members</h4>
<div class="channel-roles-member-list" id="channel-roles-member-list"></div>
<div class="channel-roles-actions" id="channel-roles-actions" style="display:none">
<h4 class="channel-roles-selected-name" id="channel-roles-selected-name"></h4>
<div class="channel-roles-current" id="channel-roles-current-roles"></div>
<div class="channel-roles-assign-area">
<div class="channel-roles-assign-row">
<select id="channel-roles-role-select" class="organize-select">
<option value="" data-i18n="modals.channel_roles.select_role">-- Select Role --</option>
</select>
<select id="channel-roles-scope-select" class="organize-select" style="max-width:140px">
<option value="server" data-i18n="modals.channel_roles.server_wide">🌐 Server-wide</option>
<option value="channel" data-i18n="modals.channel_roles.this_channel">📌 This Channel</option>
</select>
<button class="btn-sm btn-accent" id="channel-roles-assign-btn" data-i18n="modals.channel_roles.assign_btn">Assign</button>
</div>
</div>
</div>
</div>
<!-- Right: Role Configuration -->
<div class="channel-roles-right">
<h4 class="channel-roles-section-title" data-i18n="modals.channel_roles.role_config_title">Role Configuration</h4>
<div class="channel-roles-role-list" id="channel-roles-role-list"></div>
<button class="btn-sm btn-accent btn-full" id="channel-roles-create-btn" data-i18n="modals.channel_roles.new_role_btn" style="margin-top:6px">+ New Role</button>
<div class="channel-roles-role-detail" id="channel-roles-role-detail">
<p class="muted-text" style="padding:12px;text-align:center;font-size:0.82rem" data-i18n="modals.channel_roles.select_to_configure">Select a role to configure</p>
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn-sm btn-accent" id="channel-roles-done-btn" data-i18n="modals.common.done">Done</button>
</div>
</div>
</div>
<div class="modal-overlay" id="webhook-modal" style="display:none">
<div class="modal" style="width:520px">
<h3>🤖 <span data-i18n="modals.webhook.title">Webhooks</span></h3>
<p class="modal-desc" id="webhook-modal-channel-name"></p>
<!-- Create new webhook -->
<div class="form-group" style="display:flex;gap:8px;align-items:flex-end">
<div style="flex:1">
<label data-i18n="modals.webhook.name_label">New Webhook Name</label>
<input type="text" id="webhook-name-input" class="input" maxlength="32" data-i18n-placeholder="modals.webhook.name_placeholder" placeholder="My Bot" style="width:100%">
</div>
<button class="btn-sm btn-accent" id="webhook-create-btn" data-i18n="modals.webhook.create_btn" style="height:36px">Create</button>
</div>
<!-- Webhook list -->
<div id="webhook-list" style="margin-top:14px;max-height:280px;overflow-y:auto"></div>
<!-- Token reveal (shown once after creation) -->
<div id="webhook-token-reveal" style="display:none;margin-top:14px;padding:12px;border-radius:8px;background:rgba(0,0,0,0.25);border:1px solid var(--accent-color, #00e5ff)">
<p style="font-size:0.85rem;margin:0 0 8px;opacity:0.7">⚠️ <span data-i18n="modals.webhook.token_warning">Copy this URL now — you won't see the full token again!</span></p>
<div style="display:flex;gap:6px">
<input type="text" id="webhook-url-display" class="input" readonly style="flex:1;font-size:0.8rem;font-family:monospace">
<button class="btn-sm" id="webhook-copy-url-btn">📋 <span data-i18n="modals.webhook.copy_btn">Copy</span></button>
</div>
</div>
<div class="modal-actions" style="margin-top:16px">
<button class="btn-sm" id="webhook-close-btn" data-i18n="modals.common.close">Close</button>
</div>
</div>
</div>
<!-- Sub-channel Subscriptions Panel -->
<div class="modal-overlay" id="sub-panel-modal" style="display:none">
<div class="modal" style="width:520px;max-height:80vh;display:flex;flex-direction:column">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
<h3 style="margin:0;flex:1">📡 <span data-i18n="modals.sub_panel.title">Sub-channel Subscriptions</span></h3>
<button class="btn-sm" id="sub-panel-close-btn" data-i18n="modals.common.close" style="font-size:1rem;padding:4px 10px">&times;</button>
</div>
<p class="modal-desc" style="margin:0 0 12px;font-size:0.8rem;opacity:0.6" data-i18n="modals.sub_panel.desc">Subscribe to get notifications. Unsubscribed channels still appear but won't notify you.</p>
<div id="sub-panel-content" style="overflow-y:auto;flex:1;min-height:0"></div>
</div>
</div>
<!-- Create Sub-channel Modal -->
<div class="modal-overlay" id="create-sub-modal" style="display:none">
<div class="modal" style="max-width:380px">
<h3 data-i18n="modals.create_sub.title">Create Sub-channel</h3>
<p class="modal-desc" id="create-sub-parent-name"></p>
<label style="display:block;margin:12px 0 6px;font-size:0.85rem;opacity:0.7" data-i18n="modals.create_sub.name_label">Sub-channel name</label>
<input type="text" id="create-sub-name" class="input" maxlength="50" data-i18n-placeholder="modals.create_sub.name_placeholder" placeholder="sub-channel name..." style="width:100%">
<label class="checkbox-row" style="margin:14px 0 8px;display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="create-sub-private" style="width:16px;height:16px">
<span style="font-size:0.9rem">🔒 <span data-i18n="modals.create_sub.private_label">Private</span> <span style="opacity:0.5;font-size:0.8rem" data-i18n="modals.create_sub.private_hint">(invite-only, hidden from non-members)</span></span>
</label>
<label class="checkbox-row" style="margin:2px 0 8px;display:flex;align-items:center;gap:8px;cursor:pointer;font-size:0.9rem">
<input type="checkbox" id="create-sub-temporary" style="width:16px;height:16px">
<span>⏱️ <span data-i18n="modals.create_sub.temporary_label">Temporary</span> <span style="opacity:0.5;font-size:0.8rem" data-i18n="modals.create_sub.temporary_hint">(auto-delete)</span></span>
</label>
<div id="sub-temp-duration-row" style="display:none;margin:0 0 8px;padding-left:24px">
<div style="display:flex;gap:6px;align-items:center">
<input type="number" id="create-sub-duration" min="1" max="720" value="24" style="width:60px;font-size:0.8rem" placeholder="Hours">
<span style="font-size:0.75rem;opacity:0.6" data-i18n="modals.create_sub.hours">hours</span>
</div>
</div>
<p class="muted-text" style="font-size:0.75rem;margin:0 0 8px;opacity:0.6"> <span data-i18n="modals.create_sub.admin_note">Admins always have access to all channels, including private ones.</span></p>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
<button class="btn-sm" id="create-sub-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
<button class="btn-sm btn-accent" id="create-sub-confirm-btn" data-i18n="modals.create_sub.create_btn">Create</button>
</div>
</div>
</div>
<!-- Stackable effect overlay container -->
<div id="fx-layers"></div>
<!-- Push Notification Error Modal (center-screen, requires acknowledgement) -->
<!-- Activities / Games Launcher Modal -->
<div class="modal-overlay" id="activities-modal" style="display:none">
<div class="modal modal-activities">
<div class="activities-header">
<h3>🎮 <span data-i18n="modals.activities.title">Activities</span></h3>
<button class="settings-close-btn" id="close-activities-btn">&times;</button>
</div>
<div class="activities-grid" id="activities-grid">
<!-- Populated dynamically by JS from GAMES_REGISTRY -->
</div>
</div>
</div>
<!-- Iframe Game Container (fullscreen overlay) -->
<div class="game-iframe-overlay" id="game-iframe-overlay" style="display:none">
<div class="game-iframe-header">
<span class="game-iframe-title" id="game-iframe-title">Game</span>
<div class="game-iframe-actions">
<label style="display:flex;align-items:center;gap:6px;color:var(--text-secondary);font-size:12px;margin-right:8px;" title="Game Volume">
🔊 <input type="range" id="game-volume-slider" min="0" max="100" value="80" style="width:80px;accent-color:var(--accent);">
<span id="game-volume-pct" style="min-width:28px;font-size:11px;">80%</span>
</label>
<button class="btn-sm" id="game-iframe-popout" title="Open in new window">↗️ <span data-i18n="modals.game_overlay.popout_btn">Pop Out</span></button>
<button class="btn-sm btn-danger" id="game-iframe-close" data-i18n="modals.game_overlay.close_btn" title="Close game">&times; Close</button>
</div>
</div>
<iframe id="game-iframe" class="game-iframe" sandbox="allow-scripts allow-same-origin allow-popups allow-forms" allowfullscreen></iframe>
</div>
<div class="modal-overlay" id="push-error-modal" style="display:none">
<div class="modal" style="width:420px;text-align:center;padding:28px 24px;">
<div style="font-size:48px;margin-bottom:12px;">🔕</div>
<h3 style="margin:0 0 12px;color:var(--text-primary)" data-i18n="modals.push_error.title">Push Notifications Unavailable</h3>
<p id="push-error-reason" style="color:var(--text-secondary);font-size:14px;line-height:1.5;margin:0 0 16px;"></p>
<p style="color:var(--text-muted);font-size:12px;line-height:1.5;margin:0 0 20px;" data-i18n-html="modals.push_error.recommendation">
<strong>Recommended:</strong> Google Chrome, Microsoft Edge, or Opera on desktop.<br>
iOS requires Safari 16.4+ with Haven added to your Home Screen.
</p>
<button class="btn-accent" id="push-error-dismiss-btn" style="width:100%;padding:10px;font-size:14px;font-weight:600;" data-i18n="modals.push_error.got_it">Got It</button>
</div>
</div>
<!-- E2E Password Prompt Modal -->
<div id="e2e-password-modal" class="modal-overlay" style="display:none">
<div class="modal e2e-password-modal">
<h3>🔐 <span data-i18n="modals.e2e_password.title">Password Required</span></h3>
<p class="e2e-pw-desc" data-i18n="modals.e2e_password.desc">End-to-end encryption requires your password to unlock your keys. Enter your account password to continue.</p>
<div class="e2e-pw-field">
<input type="password" id="e2e-pw-input" data-i18n-placeholder="modals.e2e_password.placeholder" placeholder="Enter your password" autocomplete="current-password" spellcheck="false">
</div>
<div id="e2e-pw-error" class="e2e-pw-error" style="display:none"></div>
<div class="e2e-pw-actions">
<button class="btn-accent" id="e2e-pw-submit-btn" data-i18n="modals.e2e_password.unlock_btn">Unlock</button>
<button class="btn-sm" id="e2e-pw-cancel-btn" data-i18n="modals.common.cancel">Cancel</button>
</div>
</div>
</div>
<!-- Desktop App Promo Popup -->
<!-- Android App Promo Modal -->
<div class="modal-overlay" id="android-beta-modal" style="display:none">
<div class="modal android-beta-promo">
<div class="android-beta-promo-icon">📱</div>
<span class="android-beta-promo-badge" data-i18n="modals.android_beta.badge" style="background:var(--accent-bright,#4caf50)">NOW AVAILABLE</span>
<h3 class="android-beta-promo-title"><span data-i18n="modals.android_beta.title">Amni-Haven Android</span></h3>
<p class="android-beta-promo-subtitle" data-i18n="modals.android_beta.subtitle">A native Android app for Haven, built from the ground up by Amnibro!</p>
<ul class="android-beta-promo-features">
<li><span class="promo-check"></span> <span data-i18n="modals.android_beta.feature_native">Native Android experience</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.android_beta.feature_push">Push notifications via Google Play</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.android_beta.feature_chat">Full chat &amp; voice support</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.android_beta.feature_built">Built from the ground up for Haven</span></li>
</ul>
<p class="android-beta-promo-note" data-i18n="modals.android_beta.note">Available now on Google Play:</p>
<div class="android-beta-form">
<a id="android-beta-submit" href="https://play.google.com/store/apps/details?id=com.havenapp.mobile&gl=US" target="_blank" rel="noopener" class="btn btn-accent android-beta-submit" style="text-decoration:none;display:inline-block">📲 <span data-i18n="modals.android_beta.request_btn">Get it on Google Play</span></a>
</div>
<p class="android-beta-promo-credit" data-i18n-html="modals.android_beta.credit">Built with ❤️ by <strong>Amnibro</strong> — thank you for your incredible work on the Amni-Haven Android app!</p>
<div class="android-beta-promo-actions">
<button id="android-beta-later" class="btn android-beta-promo-later" data-i18n="modals.android_beta.later_btn">Maybe later</button>
</div>
<label class="android-beta-promo-dismiss-label">
<input type="checkbox" id="android-beta-dismiss-check"> <span data-i18n="modals.android_beta.dont_show">Don't show this again</span>
</label>
</div>
</div>
<div class="modal-overlay" id="desktop-promo-modal" style="display:none">
<div class="modal desktop-promo">
<div class="desktop-promo-icon">🖥️</div>
<h3 class="desktop-promo-title"><span data-i18n="modals.desktop_promo.title">Haven Desktop App</span> <span class="desktop-promo-badge" data-i18n="modals.desktop_promo.badge">BETA</span></h3>
<p class="desktop-promo-subtitle" data-i18n="modals.desktop_promo.subtitle">Now available for your platform!</p>
<ul class="desktop-promo-features">
<li><span class="promo-check"></span> <span data-i18n="modals.desktop_promo.feature_notifs">Native system notifications</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.desktop_promo.feature_tray">Minimize to system tray</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.desktop_promo.feature_audio">Per-app audio routing</span></li>
<li><span class="promo-check"></span> <span data-i18n="modals.desktop_promo.feature_launcher">Built-in server launcher</span></li>
</ul>
<p class="desktop-promo-meta" id="desktop-promo-meta">Windows Installer • v1.0.0</p>
<div class="desktop-promo-actions">
<a id="desktop-promo-install" class="btn btn-accent desktop-promo-install" href="https://ancsemi.github.io/Haven/#download" target="_blank" rel="noopener"><span data-i18n="modals.desktop_promo.install_btn">Install Haven</span></a>
<button id="desktop-promo-later" class="btn desktop-promo-later" data-i18n="modals.desktop_promo.later_btn">Maybe later</button>
</div>
<label class="desktop-promo-dismiss-label">
<input type="checkbox" id="desktop-promo-dismiss-check"> <span data-i18n="modals.desktop_promo.dont_show">Don't show this again</span>
</label>
</div>
</div>
<!-- Donors Thank You Modal -->
<div class="modal-overlay" id="donors-modal" style="display:none">
<div class="modal donors-modal-box">
<button class="donors-close-btn" id="donors-close-btn" title="Close">&times;</button>
<div class="donors-heart-icon">❤️</div>
<h2 class="donors-title" data-i18n="modals.donors.title">Thank You</h2>
<p class="donors-subtitle" data-i18n="modals.donors.subtitle">Haven is built with love and kept alive by the generosity of people like you. Every donation — big or small — means the world.</p>
<div class="donors-sort-toggle" id="donors-sort-toggle" style="display:none">
<button class="donors-sort-btn active" data-sort="chronological" data-i18n="modals.donors.sort_chronological">Early Believers</button>
<button class="donors-sort-btn" data-sort="featured" data-i18n="modals.donors.sort_featured">Biggest Thanks!</button>
</div>
<div class="donors-tiers" id="donors-tiers">
<div class="donors-tier">
<h3 class="donors-tier-title donors-sponsor-title"><span data-i18n="modals.donors.sponsors_title">Sponsors</span></h3>
<div class="donors-grid" id="sponsors-grid"></div>
</div>
<div class="donors-tier">
<h3 class="donors-tier-title donors-donor-title">💛 <span data-i18n="modals.donors.donors_title">Donors</span></h3>
<div class="donors-grid" id="donors-grid"></div>
</div>
</div>
<p class="donors-note" data-i18n="modals.donors.note">New donors will be added each release. If you've donated and don't see your name, it'll be in the next update!</p>
<a href="https://ko-fi.com/ancsemi" target="_blank" rel="noopener noreferrer" class="donors-kofi-btn">
<span><span data-i18n="modals.donors.kofi_btn">Support Haven on Ko-fi</span></span>
</a>
</div>
</div>
<script src="/js/i18n.js"></script>
<script>i18n.init();</script>
<script src="/socket.io/socket.io.js"></script>
<!-- Image Lightbox Overlay -->
<div id="image-lightbox" class="image-lightbox" style="display:none">
<button id="lightbox-prev" class="lightbox-nav lightbox-prev" aria-label="Previous image">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<img id="lightbox-img" class="lightbox-img" src="" alt="">
<button id="lightbox-next" class="lightbox-nav lightbox-next" aria-label="Next image">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>
</button>
</div>
<script src="/js/theme.js?v=3.4.0"></script>
<script src="/js/notifications.js?v=3.4.0"></script>
<script src="/js/servers.js?v=3.4.0"></script>
<script src="/js/voice.js?v=3.4.0"></script>
<script src="/js/modmode.js?v=3.4.0"></script>
<script src="/js/e2e.js?v=3.4.0"></script>
<script type="module" src="/js/app.js?v=3.4.0"></script>
<script src="/js/plugin-loader.js?v=3.4.0"></script>
</body>
</html>