diff --git a/backend/src/controllers/AdminController.cpp b/backend/src/controllers/AdminController.cpp index 0c1fa02..eaa4305 100644 --- a/backend/src/controllers/AdminController.cpp +++ b/backend/src/controllers/AdminController.cpp @@ -2336,41 +2336,6 @@ void AdminController::updateSiteSettings(const HttpRequestPtr &req, }; } - // Update announcement_enabled if provided - if (json->isMember("announcement_enabled")) { - bool enabled = (*json)["announcement_enabled"].asBool(); - std::string value = enabled ? "true" : "false"; - *dbClient << "INSERT INTO site_settings (setting_key, setting_value) VALUES ('announcement_enabled', $1) " - "ON CONFLICT (setting_key) DO UPDATE SET setting_value = $1, updated_at = CURRENT_TIMESTAMP" - << value - >> [](const Result&) { - LOG_INFO << "Announcement enabled setting updated successfully"; - } - >> [](const DrogonDbException& e) { - LOG_ERROR << "Failed to update announcement_enabled: " << e.base().what(); - }; - } - - // Update announcement_text if provided - if (json->isMember("announcement_text")) { - std::string text = (*json)["announcement_text"].asString(); - // Limit to 500 characters - if (text.length() > 500) { - text = text.substr(0, 500); - } - // Sanitize to prevent XSS - text = htmlEscape(text); - *dbClient << "INSERT INTO site_settings (setting_key, setting_value) VALUES ('announcement_text', $1) " - "ON CONFLICT (setting_key) DO UPDATE SET setting_value = $1, updated_at = CURRENT_TIMESTAMP" - << text - >> [](const Result&) { - LOG_INFO << "Announcement text updated successfully"; - } - >> [](const DrogonDbException& e) { - LOG_ERROR << "Failed to update announcement_text: " << e.base().what(); - }; - } - // Update censored_words if provided (comma-separated list) if (json->isMember("censored_words")) { // Rate limit: 10 updates per minute per admin @@ -2499,8 +2464,7 @@ void AdminController::getPublicSiteSettings(const HttpRequestPtr &, >> [callback](const Result& r) { // Whitelist of publicly-safe settings static const std::unordered_set publicKeys = { - "site_title", "logo_path", "logo_display_mode", - "announcement_enabled", "announcement_text" + "site_title", "logo_path", "logo_display_mode" }; Json::Value resp; diff --git a/backend/src/controllers/UserController.cpp b/backend/src/controllers/UserController.cpp index 8abe78a..4ad4fe6 100644 --- a/backend/src/controllers/UserController.cpp +++ b/backend/src/controllers/UserController.cpp @@ -2684,43 +2684,4 @@ void UserController::getReferralSettings(const HttpRequestPtr &req, callback(jsonResp(resp)); } >> DB_ERROR_MSG(callback, "get settings", "Failed to get settings"); -} - -void UserController::updateScreensaver(const HttpRequestPtr &req, - std::function &&callback) { - try { - UserInfo user = getUserFromRequest(req); - if (user.id == 0) { - callback(jsonError("Unauthorized", k401Unauthorized)); - return; - } - - auto json = req->getJsonObject(); - if (!json) { - callback(jsonError("Invalid JSON")); - return; - } - - bool enabled = (*json).isMember("enabled") ? (*json)["enabled"].asBool() : false; - int timeoutMinutes = (*json).isMember("timeout_minutes") ? (*json)["timeout_minutes"].asInt() : 5; - - // Validate timeout range (1-30 minutes) - if (timeoutMinutes < 1) timeoutMinutes = 1; - if (timeoutMinutes > 30) timeoutMinutes = 30; - - auto dbClient = app().getDbClient(); - *dbClient << "UPDATE users SET screensaver_enabled = $1, screensaver_timeout_minutes = $2 WHERE id = $3" - << enabled << timeoutMinutes << user.id - >> [callback, enabled, timeoutMinutes](const Result&) { - Json::Value resp; - resp["success"] = true; - resp["screensaver"]["enabled"] = enabled; - resp["screensaver"]["timeout_minutes"] = timeoutMinutes; - callback(jsonResp(resp)); - } - >> DB_ERROR_MSG(callback, "update screensaver settings", "Failed to update screensaver settings"); - } catch (const std::exception& e) { - LOG_ERROR << "Exception in updateScreensaver: " << e.what(); - callback(jsonError("Internal server error")); - } } \ No newline at end of file diff --git a/backend/src/controllers/UserController.h b/backend/src/controllers/UserController.h index 6084947..0a41059 100644 --- a/backend/src/controllers/UserController.h +++ b/backend/src/controllers/UserController.h @@ -48,8 +48,6 @@ public: ADD_METHOD_TO(UserController::validateReferralCode, "/api/auth/validate-referral", Post); ADD_METHOD_TO(UserController::registerWithReferral, "/api/auth/register-referral", Post); ADD_METHOD_TO(UserController::getReferralSettings, "/api/settings/referral", Get); - // Screensaver settings - ADD_METHOD_TO(UserController::updateScreensaver, "/api/user/screensaver", Put); METHOD_LIST_END void register_(const HttpRequestPtr &req, @@ -173,10 +171,6 @@ public: void getReferralSettings(const HttpRequestPtr &req, std::function &&callback); - // Screensaver settings - void updateScreensaver(const HttpRequestPtr &req, - std::function &&callback); - private: // Übercoin helper: Calculate burn rate based on account age // Formula: max(1, 99 * e^(-account_age_days / 180)) diff --git a/backend/src/services/AuthService.cpp b/backend/src/services/AuthService.cpp index b387433..5788bee 100644 --- a/backend/src/services/AuthService.cpp +++ b/backend/src/services/AuthService.cpp @@ -443,7 +443,7 @@ void AuthService::loginUser(const std::string& username, const std::string& pass return; } - *dbClient << "SELECT id, username, password_hash, is_admin, is_moderator, is_streamer, is_restreamer, is_bot, is_texter, is_pgp_only, is_disabled, bio, avatar_url, banner_url, banner_position, banner_zoom, banner_position_x, graffiti_url, pgp_only_enabled_at, user_color, screensaver_enabled, screensaver_timeout_minutes " + *dbClient << "SELECT id, username, password_hash, is_admin, is_moderator, is_streamer, is_restreamer, is_bot, is_texter, is_pgp_only, is_disabled, bio, avatar_url, banner_url, banner_position, banner_zoom, banner_position_x, graffiti_url, pgp_only_enabled_at, user_color " "FROM users WHERE username = $1 LIMIT 1" << username >> [password, callback, this](const Result& r) { @@ -504,8 +504,6 @@ void AuthService::loginUser(const std::string& username, const std::string& pass user.graffitiUrl = r[0]["graffiti_url"].isNull() ? "" : r[0]["graffiti_url"].as(); user.pgpOnlyEnabledAt = r[0]["pgp_only_enabled_at"].isNull() ? "" : r[0]["pgp_only_enabled_at"].as(); user.colorCode = r[0]["user_color"].isNull() ? "#561D5E" : r[0]["user_color"].as(); - user.screensaverEnabled = r[0]["screensaver_enabled"].isNull() ? false : r[0]["screensaver_enabled"].as(); - user.screensaverTimeoutMinutes = r[0]["screensaver_timeout_minutes"].isNull() ? 5 : r[0]["screensaver_timeout_minutes"].as(); std::string token = generateToken(user); callback(true, token, user); @@ -601,7 +599,7 @@ void AuthService::verifyPgpLogin(const std::string& username, const std::string& } *dbClient << "SELECT pk.public_key, u.id, u.username, u.is_admin, u.is_moderator, u.is_streamer, u.is_restreamer, u.is_bot, u.is_texter, " - "u.is_pgp_only, u.is_disabled, u.bio, u.avatar_url, u.banner_url, u.banner_position, u.banner_zoom, u.banner_position_x, u.graffiti_url, u.pgp_only_enabled_at, u.user_color, u.screensaver_enabled, u.screensaver_timeout_minutes " + "u.is_pgp_only, u.is_disabled, u.bio, u.avatar_url, u.banner_url, u.banner_position, u.banner_zoom, u.banner_position_x, u.graffiti_url, u.pgp_only_enabled_at, u.user_color " "FROM pgp_keys pk JOIN users u ON pk.user_id = u.id " "WHERE u.username = $1 ORDER BY pk.created_at DESC LIMIT 1" << username @@ -652,8 +650,6 @@ void AuthService::verifyPgpLogin(const std::string& username, const std::string& user.graffitiUrl = r[0]["graffiti_url"].isNull() ? "" : r[0]["graffiti_url"].as(); user.pgpOnlyEnabledAt = r[0]["pgp_only_enabled_at"].isNull() ? "" : r[0]["pgp_only_enabled_at"].as(); user.colorCode = r[0]["user_color"].isNull() ? "#561D5E" : r[0]["user_color"].as(); - user.screensaverEnabled = r[0]["screensaver_enabled"].isNull() ? false : r[0]["screensaver_enabled"].as(); - user.screensaverTimeoutMinutes = r[0]["screensaver_timeout_minutes"].isNull() ? 5 : r[0]["screensaver_timeout_minutes"].as(); std::string token = generateToken(user); callback(true, token, user); @@ -921,7 +917,7 @@ void AuthService::fetchUserInfo(int64_t userId, std::function> [callback](const Result& r) { @@ -950,8 +946,6 @@ void AuthService::fetchUserInfo(int64_t userId, std::function(); user.pgpOnlyEnabledAt = r[0]["pgp_only_enabled_at"].isNull() ? "" : r[0]["pgp_only_enabled_at"].as(); user.colorCode = r[0]["user_color"].isNull() ? "#561D5E" : r[0]["user_color"].as(); - user.screensaverEnabled = r[0]["screensaver_enabled"].isNull() ? false : r[0]["screensaver_enabled"].as(); - user.screensaverTimeoutMinutes = r[0]["screensaver_timeout_minutes"].isNull() ? 5 : r[0]["screensaver_timeout_minutes"].as(); callback(true, user); } catch (const std::exception& e) { @@ -1075,7 +1069,7 @@ void AuthService::validateAndRotateRefreshToken(const std::string& refreshToken, *dbClient << "SELECT rtf.id, rtf.user_id, rtf.family_id, rtf.expires_at, rtf.revoked, " "u.username, u.is_admin, u.is_moderator, u.is_streamer, u.is_restreamer, " "u.is_bot, u.is_texter, u.is_pgp_only, u.is_disabled, u.user_color, u.avatar_url, " - "u.token_version, u.screensaver_enabled, u.screensaver_timeout_minutes " + "u.token_version " "FROM refresh_token_families rtf " "JOIN users u ON rtf.user_id = u.id " "WHERE rtf.current_token_hash = $1" @@ -1136,8 +1130,6 @@ void AuthService::validateAndRotateRefreshToken(const std::string& refreshToken, user.colorCode = row["user_color"].isNull() ? "#561D5E" : row["user_color"].as(); user.avatarUrl = row["avatar_url"].isNull() ? "" : row["avatar_url"].as(); user.tokenVersion = row["token_version"].isNull() ? 1 : row["token_version"].as(); - user.screensaverEnabled = row["screensaver_enabled"].isNull() ? false : row["screensaver_enabled"].as(); - user.screensaverTimeoutMinutes = row["screensaver_timeout_minutes"].isNull() ? 5 : row["screensaver_timeout_minutes"].as(); // Generate new tokens (rotation) std::string newRefreshToken = generateRefreshToken(); diff --git a/backend/src/services/AuthService.h b/backend/src/services/AuthService.h index 88749de..0214c10 100644 --- a/backend/src/services/AuthService.h +++ b/backend/src/services/AuthService.h @@ -29,8 +29,6 @@ struct UserInfo { double ubercoinBalance = 0.0; // Übercoin balance (3 decimal places) std::string createdAt; // Account creation date (for burn rate calculation) int tokenVersion = 1; // SECURITY FIX #10: Token version for revocation - bool screensaverEnabled = false; // Screensaver feature enabled - int screensaverTimeoutMinutes = 5; // Idle timeout before screensaver activates (1-30) }; // Result structure for refresh token operations diff --git a/chat-service/src/controllers/ChatWebSocketController.cpp b/chat-service/src/controllers/ChatWebSocketController.cpp index 3ce4d58..c47eb1d 100644 --- a/chat-service/src/controllers/ChatWebSocketController.cpp +++ b/chat-service/src/controllers/ChatWebSocketController.cpp @@ -464,24 +464,9 @@ void ChatWebSocketController::handleChatMessage(const WebSocketConnectionPtr& ws selfDestructSeconds = 300; } - // Get connection info first to check if guest - ConnectionInfo currentInfo; - { - std::lock_guard lock(connectionsMutex_); - auto it = connections_.find(wsConnPtr); - if (it == connections_.end()) { - return; - } - currentInfo = it->second; - } - - // Guests cannot send self-destructing messages - if (currentInfo.isGuest && selfDestructSeconds > 0) { - selfDestructSeconds = 0; - } - // Re-fetch current connection info to prevent TOCTOU race conditions // (user could have been banned/disconnected since the initial copy) + ConnectionInfo currentInfo; { std::lock_guard lock(connectionsMutex_); auto it = connections_.find(wsConnPtr); diff --git a/database/init.sql b/database/init.sql index e9ee0d2..3875dba 100644 --- a/database/init.sql +++ b/database/init.sql @@ -555,9 +555,7 @@ CREATE TABLE IF NOT EXISTS site_settings ( INSERT INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'Stream'), ('logo_path', ''), - ('logo_display_mode', 'text'), -- 'text', 'image', 'both' - ('announcement_enabled', 'false'), - ('announcement_text', '') + ('logo_display_mode', 'text') -- 'text', 'image', 'both' ON CONFLICT (setting_key) DO NOTHING; -- Trigger for site_settings updated_at @@ -1224,30 +1222,4 @@ CREATE TABLE IF NOT EXISTS refresh_token_families ( CREATE INDEX IF NOT EXISTS idx_refresh_families_user_id ON refresh_token_families(user_id); CREATE INDEX IF NOT EXISTS idx_refresh_families_family_id ON refresh_token_families(family_id); CREATE INDEX IF NOT EXISTS idx_refresh_families_active ON refresh_token_families(user_id, revoked) WHERE revoked = FALSE; -CREATE INDEX IF NOT EXISTS idx_refresh_families_expires ON refresh_token_families(expires_at) WHERE revoked = FALSE; - --- ============================================ --- SCREENSAVER SETTINGS --- ============================================ - --- Add screensaver_enabled column to users table (default disabled) -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'screensaver_enabled' - ) THEN - ALTER TABLE users ADD COLUMN screensaver_enabled BOOLEAN DEFAULT false; - END IF; -END $$; - --- Add screensaver_timeout_minutes column to users table (default 5 minutes, range 1-30) -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'screensaver_timeout_minutes' - ) THEN - ALTER TABLE users ADD COLUMN screensaver_timeout_minutes INTEGER DEFAULT 5; - END IF; -END $$; \ No newline at end of file +CREATE INDEX IF NOT EXISTS idx_refresh_families_expires ON refresh_token_families(expires_at) WHERE revoked = FALSE; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 85bd0a4..4689e6d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,11 +24,13 @@ "@heroiclabs/nakama-js": "^2.8.0", "@fingerprintjs/fingerprintjs": "^4.5.1", "chess.js": "^1.0.0-beta.8", + "@fortawesome/fontawesome-free": "^6.7.2", "@types/dompurify": "^3.0.5", "dompurify": "^3.3.0", "foliate-js": "^1.0.1", "hls.js": "^1.6.7", "marked": "^17.0.1", + "mdb-ui-kit": "^9.1.0", "openpgp": "^6.0.0-alpha.0", "ovenplayer": "^0.10.43" }, diff --git a/frontend/src/app.css b/frontend/src/app.css index ed8a61b..cd27d7c 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -5,63 +5,14 @@ } :root { - /* Primary */ --primary: #561d5e; - --primary-light: #8b3a92; - - /* Legacy aliases (for backwards compatibility) */ --black: #000; --white: #fff; --gray: #888; --light-gray: #f5f5f5; - - /* Backgrounds (dark to light) */ - --bg-base: #000; - --bg-surface: #0d0d0d; - --bg-elevated: #111; - --bg-input: #1a1a1a; - --bg-hover: rgba(255, 255, 255, 0.05); - --bg-hover-light: rgba(255, 255, 255, 0.1); - - /* Text */ - --text-primary: #fff; - --text-secondary: #c9d1d9; - --text-muted: #888; - --text-faint: #666; - - /* Borders */ - --border: #333; - --border-light: #30363d; - --border-hover: #484f58; - - /* Accents */ - --accent-blue: #4a9eff; - --accent-pink: #ec4899; - --accent-pink-light: #f472b6; - --accent-green: #0f0; - --accent-orange: #ff9800; - --accent-gold: #ffd700; - - /* Semantic */ - --success: #28a745; --error: #dc3545; - --error-light: #f85149; - --warning: #ff9800; - - /* Chat-specific */ - --mention: #ffd700; - --greentext: #789922; - --redtext: #cc1105; - - /* Role Colors */ - --role-admin: #ef4444; - --role-moderator: #a855f7; - --role-streamer: #3b82f6; - --role-restreamer: #14b8a6; - --role-uploader: #22c55e; - --role-texter: #fb923c; - --role-sticker: #ec4899; - --role-watch: #6366f1; + --success: #28a745; + --border: #333; } html { @@ -100,7 +51,7 @@ body::-webkit-scrollbar { max-width: 400px; margin: 4rem auto; padding: 2rem; - background: var(--bg-elevated); + background: #111; border-radius: 8px; border: 1px solid var(--border); } @@ -190,7 +141,7 @@ button:disabled { } .nav { - background: var(--bg-elevated); + background: #111; border-bottom: 1px solid var(--border); padding: 1rem 0; margin-bottom: 2rem; @@ -214,7 +165,7 @@ button:disabled { .card { - background: var(--bg-elevated); + background: #111; border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; @@ -227,6 +178,18 @@ button:disabled { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } +.avatar { + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; +} + +.avatar-small { + width: 40px; + height: 40px; +} + .file-input-wrapper { position: relative; overflow: hidden; @@ -297,7 +260,7 @@ button:disabled { } .modal-content { - background: var(--bg-elevated); + background: #111; border: 1px solid var(--border); border-radius: 8px; padding: 2rem; diff --git a/frontend/src/lib/components/ScreensaverOverlay.svelte b/frontend/src/lib/components/ScreensaverOverlay.svelte deleted file mode 100644 index 56e5122..0000000 --- a/frontend/src/lib/components/ScreensaverOverlay.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - -{#if $isScreensaverActive} -
-
- {#each snowflakes as flake (flake.id)} -
- {/each} -
- -
- Click or press any key to dismiss -
-
-{/if} - - diff --git a/frontend/src/lib/components/chat/ChatInput.svelte b/frontend/src/lib/components/chat/ChatInput.svelte index 9995735..5176d0e 100644 --- a/frontend/src/lib/components/chat/ChatInput.svelte +++ b/frontend/src/lib/components/chat/ChatInput.svelte @@ -5,7 +5,6 @@ export let disabled = false; export let username = ''; - export let isGuest = false; const dispatch = createEventDispatcher(); @@ -267,40 +266,38 @@
- {#if !isGuest} -
- - {#if showTimerMenu} -
- {#each timerOptions as option} - - {/each} -
+
+
- {/if} + + {#if showTimerMenu} +
+ {#each timerOptions as option} + + {/each} +
+ {/if} +
{#if $stickerFavorites.length > 0}
-{#if $siteSettings.announcement_enabled && $siteSettings.announcement_text} -
- {$siteSettings.announcement_text} -
-{/if} - {#if browser} - {/if} {/if} diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index 0836134..34978f1 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -57,11 +57,6 @@ lastRenewalError: null, autoRenewalEnabled: true }; - - // Announcement Settings - let announcementEnabled = false; - let announcementText = ''; - let announcementSaving = false; let sslLoading = false; let sslSaving = false; let sslRequesting = false; @@ -1147,8 +1142,6 @@ logo_display_mode: settings.logo_display_mode || 'text' }; censoredWords = settings.censored_words || ''; - announcementEnabled = settings.announcement_enabled === 'true'; - announcementText = settings.announcement_text || ''; } else { console.error('Failed to load site settings'); } @@ -1228,35 +1221,6 @@ setTimeout(() => { message = ''; error = ''; }, 3000); } - async function saveAnnouncementSettings() { - announcementSaving = true; - try { - const response = await fetch('/api/admin/settings/site', { - method: 'PUT', - credentials: 'include', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - announcement_enabled: announcementEnabled, - announcement_text: announcementText - }) - }); - - if (response.ok) { - message = 'Announcement settings updated successfully'; - } else { - error = 'Failed to update announcement settings'; - } - } catch (e) { - error = 'Error updating announcement settings'; - console.error(e); - } - announcementSaving = false; - - setTimeout(() => { message = ''; error = ''; }, 3000); - } - async function loadDefaultAvatars() { try { const response = await fetch('/api/admin/default-avatars', { @@ -3639,57 +3603,6 @@ {/if}
- -
-

Site Announcement

-

- Display a site-wide announcement banner at the top of all pages -

- -
-
- - - When enabled, the announcement will be displayed at the top of all pages - -
- -
- - - - {announcementText.length}/500 characters. This message will be displayed to all visitors. - -
- -
- -
-
-
- {:else if activeTab === 'botkeys'}

Bot API Keys

diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index 0cbf215..7ba2c1b 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -3,7 +3,6 @@ import { browser } from '$app/environment'; import { auth, isAuthenticated, isBot, isStickerCreator } from '$lib/stores/auth'; import { siteSettings } from '$lib/stores/siteSettings'; - import { screensaver } from '$lib/stores/screensaver'; import { goto } from '$app/navigation'; import * as pgp from '$lib/pgp'; import GraffitiEditor from '$lib/components/GraffitiEditor.svelte'; @@ -109,13 +108,6 @@ let referralPurchasing = false; let referralSystemEnabled = false; - // Screensaver settings - let screensaverEnabled = false; - let screensaverTimeoutMinutes = 5; - let screensaverLoading = false; - let screensaverMessage = ''; - let screensaverError = ''; - // User data for safe access let currentUser = null; @@ -159,8 +151,6 @@ graffitiUrl = data.user.graffitiUrl || ''; userColor = data.user.colorCode || '#561D5E'; newColor = userColor; - screensaverEnabled = data.user.screensaverEnabled || false; - screensaverTimeoutMinutes = data.user.screensaverTimeoutMinutes || 5; currentUser = data.user; // Update auth store with fresh data @@ -574,40 +564,6 @@ setTimeout(() => { referralMessage = ''; }, 3000); } - async function updateScreensaverSettings() { - screensaverLoading = true; - screensaverError = ''; - screensaverMessage = ''; - - try { - const response = await fetch('/api/user/screensaver', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - enabled: screensaverEnabled, - timeout_minutes: screensaverTimeoutMinutes - }) - }); - - const data = await response.json(); - - if (response.ok && data.success) { - screensaverMessage = 'Screensaver settings saved'; - // Update the screensaver store - screensaver.updateSettings(screensaverEnabled, screensaverTimeoutMinutes); - setTimeout(() => { screensaverMessage = ''; }, 3000); - } else { - screensaverError = data.error || 'Failed to save settings'; - } - } catch (e) { - screensaverError = 'Error saving settings'; - console.error(e); - } - - screensaverLoading = false; - } - function validatePassword(pass) { if (pass.length < 8) { return 'Password must be at least 8 characters'; @@ -2240,13 +2196,6 @@ Referrals {/if} -
{#if activeTab === 'profile'} @@ -3262,74 +3211,6 @@ bot.connect(); {/if} {/if}
- - {:else if activeTab === 'screensaver'} -
-

Screensaver Settings

-

- Enable a snowfall screensaver that activates when you're idle. -

- - {#if screensaverMessage} -
{screensaverMessage}
- {/if} - - {#if screensaverError} -
{screensaverError}
- {/if} - - -
- -

- When enabled, a snowfall animation will appear after the idle timeout. -

-
- - {#if screensaverEnabled} -
- - -

- Time of inactivity before the screensaver activates (1-30 minutes). -

-
- -
-

- Note: The screensaver will not activate while: -

-
    -
  • Video or audio is playing
  • -
  • The browser tab is not visible
  • -
-
- {/if} - -
- -
- -
{/if}