4sp-dv-latest 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  <title>4SP - VERSION 5 CLIENT</title>
7
7
  <link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/npm/4sp-dv@latest/images/logo.png">
8
8
 
9
- <base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.15/logged-in/">
9
+ <base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.19/logged-in/">
10
10
  <script src="https://cdn.tailwindcss.com"></script>
11
11
  <link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap" rel="stylesheet">
12
12
 
@@ -162,15 +162,14 @@
162
162
  }
163
163
  .icon-btn:hover { background-color: #374151; color: white; }
164
164
 
165
- .auth-menu, .pin-context-menu {
165
+ .auth-menu {
166
166
  position: absolute; right: 0; top: 50px; width: 14rem;
167
167
  background: var(--menu-bg, #000);
168
168
  border: 1px solid var(--menu-border, #374151);
169
169
  border-radius: 0.75rem;
170
170
  padding: 0.5rem; display: none; flex-direction: column; gap: 0.25rem; z-index: 50;
171
171
  }
172
- .pin-context-menu { right: auto; left: 0; width: 12rem; }
173
- .auth-menu.open, .pin-context-menu.open { display: flex; }
172
+ .auth-menu.open { display: flex; }
174
173
 
175
174
  .auth-menu-item {
176
175
  display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem;
@@ -261,6 +260,18 @@
261
260
  color: #6366f1;
262
261
  }
263
262
 
263
+ .btn-toolbar-style.btn-primary-override-danger {
264
+ justify-content: center;
265
+ background-color: rgba(220, 38, 38, 0.1);
266
+ border: 1px solid #dc2626;
267
+ color: #dc2626;
268
+ }
269
+ .btn-toolbar-style.btn-primary-override-danger:hover {
270
+ background-color: rgba(220, 38, 38, 0.15);
271
+ border-color: #ef4444;
272
+ color: #ef4444;
273
+ }
274
+
264
275
  .settings-tab {
265
276
  width: 100%;
266
277
  justify-content: flex-start;
@@ -410,23 +421,6 @@
410
421
  </div>
411
422
 
412
423
  <div class="auth-controls-wrapper">
413
- <div class="relative" id="pin-wrapper">
414
- <button id="pin-btn" class="icon-btn" title="Pin this page">
415
- <i class="fa-solid fa-thumbtack"></i>
416
- </button>
417
- <div id="pin-menu" class="pin-context-menu">
418
- <div class="auth-menu-item" id="repin-btn">
419
- <i class="fa-solid fa-thumbtack w-4"></i> Repin
420
- </div>
421
- <div class="auth-menu-item text-red-400 hover:text-red-300" id="remove-pin-btn">
422
- <i class="fa-solid fa-xmark w-4"></i> Remove Pin
423
- </div>
424
- <div class="auth-menu-item text-red-400 hover:text-red-300" id="hide-pin-btn">
425
- <i class="fa-solid fa-eye-slash w-4"></i> Hide Button
426
- </div>
427
- </div>
428
- </div>
429
-
430
424
  <div class="relative">
431
425
  <button id="auth-btn" class="icon-btn">
432
426
  <i class="fa-solid fa-user"></i>
@@ -439,9 +433,6 @@
439
433
  <a href="#" class="auth-menu-item" onclick="loadPage('settings.html'); return false;">
440
434
  <i class="fa-solid fa-gear w-4"></i> Settings
441
435
  </a>
442
- <div class="auth-menu-item hidden" id="show-pin-menu-item">
443
- <i class="fa-solid fa-eye w-4"></i> Show Pin Button
444
- </div>
445
436
  <div class="auth-menu-item text-red-400 hover:text-red-300" id="logout-btn">
446
437
  <i class="fa-solid fa-right-from-bracket w-4"></i> Disconnect
447
438
  </div>
@@ -474,6 +465,7 @@
474
465
  <div id="settings-main-view">
475
466
  <div id="tab-general" class="settings-section">
476
467
  <h3 class="text-3xl font-bold text-white mb-6">General Settings</h3>
468
+
477
469
  <div class="settings-box">
478
470
  <h3 class="text-xl font-bold text-white mb-2">Account Username</h3>
479
471
  <label class="block text-gray-400 text-sm mb-2 font-light">New Username</label>
@@ -483,6 +475,28 @@
483
475
  </div>
484
476
  <p id="username-msg" class="general-message-area text-sm mt-2"></p>
485
477
  </div>
478
+
479
+ <h3 class="text-xl font-bold text-white mb-2 mt-6">Organization</h3>
480
+ <div class="settings-box bg-cyan-500/10 border-cyan-500/50 p-4">
481
+ <p class="text-sm font-light text-cyan-300 mb-3">
482
+ Visit the official 4SP Organization website for updates and more tools.
483
+ </p>
484
+ <a href="https://4sp-organization.github.io/" target="_blank" class="btn-toolbar-style w-full justify-center bg-cyan-500/10 border-cyan-500 text-cyan-500 hover:bg-cyan-500/20 hover:text-cyan-400">
485
+ <i class="fa-solid fa-globe mr-2"></i> Visit Website
486
+ </a>
487
+ </div>
488
+
489
+ <h3 class="text-xl font-bold text-white mb-2 mt-6">Disconnect Client</h3>
490
+ <div class="settings-box bg-red-900/10 border-red-700/50 p-4">
491
+ <p class="text-sm font-light text-red-300 mb-3">
492
+ <i class="fa-solid fa-triangle-exclamation mr-1"></i>
493
+ Disconnect this client from your account. You will need to generate a new code to reconnect.
494
+ </p>
495
+ <button id="disconnect-account-btn" class="btn-toolbar-style btn-primary-override-danger w-full justify-center">
496
+ <i class="fa-solid fa-power-off mr-2"></i> Disconnect Account
497
+ </button>
498
+ </div>
499
+
486
500
  </div>
487
501
 
488
502
  <div id="tab-personalization" class="settings-section hidden">
@@ -569,18 +583,19 @@
569
583
  <h3 class="text-3xl font-bold text-white mb-6">About 4SP</h3>
570
584
  <div class="settings-box p-6 flex flex-col items-center text-center">
571
585
  <img src="https://cdn.jsdelivr.net/npm/4sp-asset-library@latest/logo.png" class="h-24 mb-4" alt="Logo">
572
- <h1 class="text-3xl font-bold text-white mb-2">4SimpleProblems</h1>
573
- <p class="text-gray-400 mb-6 max-w-lg font-light">A comprehensive student toolkit and entertainment platform.</p>
586
+ <h1 class="text-3xl font-bold text-white mb-2">(4SP) 4simpleproblems</h1>
587
+ <p class="text-gray-400 mb-6 max-w-lg font-light">From a soundboard, to a full downloadable client of a platform.</p>
574
588
 
575
- <div class="inline-block bg-[#0a0a0a] border border-[#333] px-4 py-2 rounded-full mb-8">
576
- <span class="text-gray-500 text-sm">Version</span>
577
- <span class="text-indigo-400 font-mono font-bold ml-2">5.0.0 DV</span>
578
- </div>
589
+ <div class="inline-block bg-[#0a0a0a] border border-[#333] px-4 py-2 rounded-[14px] mb-8">
590
+ <span class="text-gray-500 text-sm">Version:</span>
591
+ <span class="text-indigo-400 font-mono font-bold ml-2">5.0.0 (DV)</span>
592
+ </div>
593
+
579
594
 
580
595
  <div class="flex gap-4">
581
- <a href="#" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-github fa-xl"></i></a>
582
- <a href="#" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-youtube fa-xl"></i></a>
583
- <a href="#" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-discord fa-xl"></i></a>
596
+ <a href="https://github.com/4simpleproblems-v5" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-github fa-xl"></i></a>
597
+ <a href="https://youtube.com/4simpleproblems" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-youtube fa-xl"></i></a>
598
+ <a href="https://x.com/@4simpleproblems" class="text-gray-400 hover:text-white transition"><i class="fa-brands fa-discord fa-xl"></i></a>
584
599
  </div>
585
600
  </div>
586
601
  </div>
@@ -1806,15 +1821,6 @@
1806
1821
  const glideLeft = document.getElementById('glide-left');
1807
1822
  const glideRight = document.getElementById('glide-right');
1808
1823
 
1809
- // Pin Elements
1810
- const pinWrapper = document.getElementById('pin-wrapper');
1811
- const pinBtn = document.getElementById('pin-btn');
1812
- const pinMenu = document.getElementById('pin-menu');
1813
- const repinBtn = document.getElementById('repin-btn');
1814
- const removePinBtn = document.getElementById('remove-pin-btn');
1815
- const hidePinBtn = document.getElementById('hide-pin-btn');
1816
- const showPinMenuItem = document.getElementById('show-pin-menu-item');
1817
-
1818
1824
  // Auth Elements
1819
1825
  const authBtn = document.getElementById('auth-btn');
1820
1826
  const authMenu = document.getElementById('auth-menu');
@@ -1822,10 +1828,8 @@
1822
1828
  const displayUsername = document.getElementById('display-username');
1823
1829
  const displayEmail = document.getElementById('display-email');
1824
1830
 
1825
- const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.15/logged-in/';
1831
+ const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.19/logged-in/';
1826
1832
  const STORAGE_KEY = 'local_access_code';
1827
- const PINNED_PAGE_KEY = 'local_pinned_page';
1828
- const PIN_HIDDEN_KEY = 'local_pin_hidden';
1829
1833
  const USER_DATA_KEY = 'local_user_data';
1830
1834
  const LAST_PAGE_KEY = 'local_last_page';
1831
1835
  const OWNER_EMAIL = "4simpleproblems@gmail.com"; // For admin checks
@@ -1844,8 +1848,9 @@
1844
1848
  };
1845
1849
 
1846
1850
  let currentUser = null;
1851
+ let userDataUnsubscribe = null;
1847
1852
 
1848
- // --- ADMIN KEYBINDS LOGIC (Integrated) ---
1853
+ // --- ADMIN KEYBINDS LOGIC (Updated to Match admin_keybinds.js exactly) ---
1849
1854
  function initAdminKeybinds(db, auth, user) {
1850
1855
  console.log("[Admin Keybinds] Initializing for", user.uid);
1851
1856
  let explicitEnabled = true;
@@ -1860,7 +1865,7 @@
1860
1865
  const toast = document.createElement("div");
1861
1866
  toast.id = "admin-keybind-toast";
1862
1867
 
1863
- // Styles
1868
+ // Styles matching admin_keybinds.js exactly
1864
1869
  Object.assign(toast.style, {
1865
1870
  position: "fixed",
1866
1871
  bottom: "24px",
@@ -1868,12 +1873,12 @@
1868
1873
  display: "flex",
1869
1874
  alignItems: "center",
1870
1875
  gap: "12px",
1871
- backgroundColor: "rgba(13, 13, 13, 0.95)",
1876
+ backgroundColor: "rgba(13, 13, 13, 0.95)", // Dark glass effect
1872
1877
  backdropFilter: "blur(5px)",
1873
- color: "#c0c0c0",
1878
+ color: "#c0c0c0", // Light gray text
1874
1879
  padding: "14px 20px",
1875
1880
  borderRadius: "12px",
1876
- border: "1px solid #333",
1881
+ border: "1px solid #333", // Dark border
1877
1882
  fontFamily: "'Geist', 'Roboto', sans-serif",
1878
1883
  fontSize: "14px",
1879
1884
  fontWeight: "500",
@@ -1919,8 +1924,10 @@
1919
1924
  const data = docSnap.data();
1920
1925
  if (data.explicitEnabled !== undefined) {
1921
1926
  explicitEnabled = data.explicitEnabled;
1927
+ console.log(`[Admin Keybinds] Explicit Enabled: ${explicitEnabled}`);
1922
1928
  }
1923
1929
  } else {
1930
+ console.log("[Admin Keybinds] Config doc missing. Defaulting to TRUE.");
1924
1931
  explicitEnabled = true;
1925
1932
  }
1926
1933
  }, (error) => console.error("Config Listen Error:", error));
@@ -1931,11 +1938,14 @@
1931
1938
 
1932
1939
  // Key Listener
1933
1940
  document.addEventListener('keydown', async (e) => {
1934
- if (e.ctrlKey && e.altKey && (e.key.toLowerCase() === 'e' || e.code === 'KeyE')) {
1941
+ // CHANGED: Trigger is Ctrl + Shift + E as requested
1942
+ if (e.ctrlKey && e.shiftKey && (e.key.toLowerCase() === 'e' || e.code === 'KeyE')) {
1935
1943
  e.preventDefault();
1936
- console.log("[Admin Keybinds] Ctrl+Alt+E detected.");
1944
+ console.log("[Admin Keybinds] Ctrl+Shift+E detected.");
1937
1945
 
1938
1946
  if (explicitEnabled) {
1947
+ // Logic for disabling (Immediate)
1948
+ console.log("[Admin Keybinds] Disabling explicit sounds...");
1939
1949
  try {
1940
1950
  await setDoc(doc(db, 'config', 'soundboard'), { explicitEnabled: false }, { merge: true });
1941
1951
  showAdminToast("Explicit Sounds: DISABLED", "red");
@@ -1945,8 +1955,10 @@
1945
1955
  showAdminToast("Error: Check Console", "red");
1946
1956
  }
1947
1957
  } else {
1958
+ // Logic for enabling (Double Press Safety)
1948
1959
  const now = Date.now();
1949
1960
  if (now - lastEnablePressTime < 1500) {
1961
+ console.log("[Admin Keybinds] Enabling explicit sounds...");
1950
1962
  try {
1951
1963
  await setDoc(doc(db, 'config', 'soundboard'), { explicitEnabled: true }, { merge: true });
1952
1964
  showAdminToast("Explicit Sounds: ENABLED", "green");
@@ -1956,7 +1968,8 @@
1956
1968
  showAdminToast("Error: Check Console", "red");
1957
1969
  }
1958
1970
  } else {
1959
- showAdminToast("Press Ctrl+Alt+E again to ENABLE", "blue");
1971
+ console.log("[Admin Keybinds] Waiting for confirm...");
1972
+ showAdminToast("Press Ctrl+Shift+E again to ENABLE", "blue");
1960
1973
  lastEnablePressTime = now;
1961
1974
  }
1962
1975
  }
@@ -2038,9 +2051,6 @@
2038
2051
  // --- Navbar Logic ---
2039
2052
  function renderNavbar(activePageUrl) {
2040
2053
  const tabsContainerEl = document.getElementById('tabs-container');
2041
- const pinBtnEl = document.getElementById('pin-btn');
2042
- const pinWrapperEl = document.getElementById('pin-wrapper');
2043
- const showPinMenuItemEl = document.getElementById('show-pin-menu-item');
2044
2054
 
2045
2055
  // Glide Elements
2046
2056
  const glideLeft = document.getElementById('glide-left');
@@ -2105,86 +2115,6 @@
2105
2115
 
2106
2116
  // Initial check
2107
2117
  setTimeout(updateScrollGilders, 50);
2108
-
2109
- // Update Pin Button State
2110
- const pinned = localStorage.getItem(PINNED_PAGE_KEY);
2111
- if (pinBtnEl) {
2112
- if (pinned && pinned === currentPath) {
2113
- pinBtnEl.style.color = '#4f46e5';
2114
- } else {
2115
- pinBtnEl.style.color = '#d1d5db';
2116
- }
2117
- }
2118
-
2119
- // Update Pin Visibility
2120
- const isHidden = localStorage.getItem(PIN_HIDDEN_KEY) === 'true';
2121
- if (isHidden) {
2122
- if (pinWrapperEl) pinWrapperEl.classList.add('hidden');
2123
- if (showPinMenuItemEl) {
2124
- showPinMenuItemEl.classList.remove('hidden');
2125
- showPinMenuItemEl.onclick = () => {
2126
- localStorage.setItem(PIN_HIDDEN_KEY, 'false');
2127
- renderNavbar(activePageUrl);
2128
- };
2129
- }
2130
- } else {
2131
- if (pinWrapperEl) pinWrapperEl.classList.remove('hidden');
2132
- if (showPinMenuItemEl) showPinMenuItemEl.classList.add('hidden');
2133
- }
2134
- }
2135
-
2136
- // --- Pin Logic ---
2137
- function getCurrentPage() {
2138
- const currentFrameSrc = appFrame.contentWindow.location.href;
2139
- let pageName = currentFrameSrc.replace(BASE_URL, '');
2140
- if (!pageName || pageName === 'about:blank') pageName = window.lastLoadedPage || 'dashboard.html';
2141
- return pageName;
2142
- }
2143
-
2144
- // Pin Button
2145
- if (pinBtn) {
2146
- pinBtn.addEventListener('click', () => {
2147
- const pageName = getCurrentPage();
2148
- const currentPinned = localStorage.getItem(PINNED_PAGE_KEY);
2149
-
2150
- if (currentPinned === pageName) {
2151
- localStorage.removeItem(PINNED_PAGE_KEY);
2152
- } else {
2153
- localStorage.setItem(PINNED_PAGE_KEY, pageName);
2154
- }
2155
- renderNavbar(pageName);
2156
- });
2157
-
2158
- // Context Menu
2159
- pinBtn.addEventListener('contextmenu', (e) => {
2160
- e.preventDefault();
2161
- if (pinMenu) pinMenu.classList.toggle('open');
2162
- if (authMenu) authMenu.classList.remove('open');
2163
- });
2164
- }
2165
-
2166
- if (repinBtn) {
2167
- repinBtn.addEventListener('click', () => {
2168
- localStorage.setItem(PINNED_PAGE_KEY, getCurrentPage());
2169
- renderNavbar(getCurrentPage());
2170
- if (pinMenu) pinMenu.classList.remove('open');
2171
- });
2172
- }
2173
-
2174
- if (removePinBtn) {
2175
- removePinBtn.addEventListener('click', () => {
2176
- localStorage.removeItem(PINNED_PAGE_KEY);
2177
- renderNavbar(getCurrentPage());
2178
- if (pinMenu) pinMenu.classList.remove('open');
2179
- });
2180
- }
2181
-
2182
- if (hidePinBtn) {
2183
- hidePinBtn.addEventListener('click', () => {
2184
- localStorage.setItem(PIN_HIDDEN_KEY, 'true');
2185
- renderNavbar(getCurrentPage());
2186
- if (pinMenu) pinMenu.classList.remove('open');
2187
- });
2188
2118
  }
2189
2119
 
2190
2120
  // Auth Menu
@@ -2192,7 +2122,6 @@
2192
2122
  authBtn.addEventListener('click', (e) => {
2193
2123
  e.stopPropagation();
2194
2124
  if (authMenu) authMenu.classList.toggle('open');
2195
- if (pinMenu) pinMenu.classList.remove('open');
2196
2125
  });
2197
2126
  }
2198
2127
 
@@ -2200,42 +2129,56 @@
2200
2129
  if (authMenu && authBtn && !authMenu.contains(e.target) && !authBtn.contains(e.target)) {
2201
2130
  authMenu.classList.remove('open');
2202
2131
  }
2203
- if (pinMenu && pinBtn && !pinMenu.contains(e.target) && !pinBtn.contains(e.target)) {
2204
- pinMenu.classList.remove('open');
2205
- }
2206
2132
  });
2207
2133
 
2208
2134
  if (logoutBtn) {
2209
2135
  logoutBtn.addEventListener('click', () => {
2210
2136
  localStorage.removeItem(STORAGE_KEY);
2211
2137
  localStorage.removeItem(USER_DATA_KEY);
2138
+ if (userDataUnsubscribe) userDataUnsubscribe();
2212
2139
  location.reload();
2213
2140
  });
2214
2141
  }
2215
2142
 
2216
2143
  async function fetchUserData(ownerUid) {
2217
- try {
2218
- // Parallel check for User Data and Admin Status
2219
- const [userDoc, adminDoc] = await Promise.all([
2220
- getDoc(doc(db, "users", ownerUid)),
2221
- getDoc(doc(db, "admins", ownerUid))
2222
- ]);
2223
-
2224
- if (userDoc.exists()) {
2225
- const data = userDoc.data();
2226
- const userData = {
2227
- uid: ownerUid,
2228
- username: data.username || "User",
2229
- email: data.email || "No email"
2230
- };
2231
- localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
2232
- updateUIWithUser(userData);
2233
- currentUser = userData; // Set global
2234
- }
2144
+ // Setup Real-time Listener for User Data (PFP Sync)
2145
+ if (userDataUnsubscribe) userDataUnsubscribe();
2235
2146
 
2236
- // Check Admin Status
2147
+ try {
2148
+ // Initial fetch to check admin status (less frequent change)
2149
+ const adminDoc = await getDoc(doc(db, "admins", ownerUid));
2150
+
2151
+ userDataUnsubscribe = onSnapshot(doc(db, "users", ownerUid), (docSnap) => {
2152
+ if (docSnap.exists()) {
2153
+ const data = docSnap.data();
2154
+ // Merge with existing local data to preserve properties not in basic user doc if any
2155
+ const newUserData = {
2156
+ ...currentUser,
2157
+ uid: ownerUid,
2158
+ username: data.username || "User",
2159
+ email: data.email || "No email",
2160
+ pfpType: data.pfpType,
2161
+ customPfp: data.customPfp,
2162
+ pfpLetterBg: data.pfpLetterBg,
2163
+ navbarTheme: data.navbarTheme // Sync theme if updated remotely
2164
+ };
2165
+
2166
+ localStorage.setItem(USER_DATA_KEY, JSON.stringify(newUserData));
2167
+ updateUIWithUser(newUserData);
2168
+ currentUser = newUserData; // Update global state
2169
+
2170
+ // If settings overlay is open, we might need to update preview there too
2171
+ if (!document.getElementById('settings-overlay').classList.contains('hidden')) {
2172
+ // Dispatch event or manually update if elements exist
2173
+ const preview = document.getElementById('pfp-preview');
2174
+ if (preview) updateSettingsPfpPreview(newUserData, preview);
2175
+ }
2176
+ }
2177
+ });
2178
+
2179
+ // Check Admin Status (kept separate as it's static usually)
2237
2180
  let isAdmin = false;
2238
- if (userData && userData.email === OWNER_EMAIL) isAdmin = true;
2181
+ if (currentUser && currentUser.email === OWNER_EMAIL) isAdmin = true;
2239
2182
  if (adminDoc.exists()) isAdmin = true;
2240
2183
 
2241
2184
  if (isAdmin) {
@@ -2250,11 +2193,27 @@
2250
2193
  initAnalytics(db, ownerUid);
2251
2194
 
2252
2195
  } catch (e) {
2253
- console.error("Error fetching user data:", e);
2254
- // Even on error, try init analytics as anon/fallback
2196
+ console.error("Error setting up user listener:", e);
2255
2197
  initAnalytics(db, ownerUid);
2256
2198
  }
2257
2199
  }
2200
+
2201
+ // Helper to update the settings panel preview if open
2202
+ function updateSettingsPfpPreview(user, previewEl) {
2203
+ const pfpType = user.pfpType || 'letter';
2204
+ if (pfpType === 'custom' && user.customPfp) {
2205
+ previewEl.style.backgroundImage = `url('${user.customPfp}')`;
2206
+ previewEl.style.backgroundColor = "transparent";
2207
+ previewEl.style.backgroundSize = "cover";
2208
+ previewEl.innerText = "";
2209
+ } else {
2210
+ const letter = (user.username || 'U').charAt(0).toUpperCase();
2211
+ const bgColor = user.pfpLetterBg || '#3B82F6';
2212
+ previewEl.style.backgroundImage = 'none';
2213
+ previewEl.style.backgroundColor = bgColor;
2214
+ previewEl.innerText = letter;
2215
+ }
2216
+ }
2258
2217
 
2259
2218
  function updateUIWithUser(userData) {
2260
2219
  if (!userData) return;
@@ -2397,15 +2356,11 @@
2397
2356
  // Determine start page
2398
2357
  let startPage = 'dashboard.html';
2399
2358
  const lastPage = localStorage.getItem(LAST_PAGE_KEY);
2400
- const pinned = localStorage.getItem(PINNED_PAGE_KEY);
2401
-
2402
- // Priority: Last Page > Pinned > Default
2359
+
2360
+ // Priority: Last Page > > Default
2403
2361
  if (lastPage) {
2404
2362
  const isValid = Object.values(PAGE_DATA).some(p => p.url === lastPage);
2405
2363
  if (isValid) startPage = lastPage;
2406
- } else if (pinned) {
2407
- const isValid = Object.values(PAGE_DATA).some(p => p.url === pinned);
2408
- if (isValid) startPage = pinned;
2409
2364
  }
2410
2365
 
2411
2366
  loadPage(startPage);
@@ -2637,11 +2592,20 @@
2637
2592
 
2638
2593
  // --- Settings Data & Actions ---
2639
2594
 
2640
- // --- 1. General: Username ---
2595
+ // --- 1. General: Username & New Actions ---
2641
2596
  const settingsUsernameInput = document.getElementById('settings-username-input');
2642
2597
  const saveUsernameBtn = document.getElementById('save-username-btn');
2643
2598
  const usernameMsg = document.getElementById('username-msg');
2644
2599
 
2600
+ // Add Disconnect Listener
2601
+ document.getElementById('disconnect-account-btn').addEventListener('click', () => {
2602
+ // Re-use existing logout logic
2603
+ localStorage.removeItem(STORAGE_KEY);
2604
+ localStorage.removeItem(USER_DATA_KEY);
2605
+ if (userDataUnsubscribe) userDataUnsubscribe();
2606
+ location.reload();
2607
+ });
2608
+
2645
2609
  async function checkProfanity(text) {
2646
2610
  try {
2647
2611
  const res = await fetch(`https://www.purgomalum.com/service/containsprofanity?text=${encodeURIComponent(text)}`);
@@ -2679,9 +2643,7 @@
2679
2643
 
2680
2644
  // Save
2681
2645
  await updateDoc(doc(db, "users", currentUser.uid), { username: newName });
2682
- currentUser.username = newName;
2683
- localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
2684
- updateUIWithUser(currentUser);
2646
+ // Local update happens via onSnapshot listener now
2685
2647
  usernameMsg.innerText = "Saved!";
2686
2648
  usernameMsg.className = "text-green-400 text-xs mt-2 min-h-[20px]";
2687
2649
 
@@ -2754,10 +2716,7 @@
2754
2716
  pfpLetterBg: pfpState.letterColor,
2755
2717
  letterAvatarColor: pfpState.letterColor // Legacy support
2756
2718
  });
2757
- // Update local logic if needed
2758
- currentUser.pfpType = 'letter';
2759
- currentUser.pfpLetterBg = pfpState.letterColor;
2760
- localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
2719
+ // Local update handled by snapshot
2761
2720
  saveLetterPfpBtn.innerText = "Saved!";
2762
2721
  setTimeout(() => saveLetterPfpBtn.innerText = "Set Letter Avatar", 2000);
2763
2722
  } catch(e) {
@@ -2923,21 +2882,21 @@
2923
2882
  try {
2924
2883
  submitCropBtn.disabled = true;
2925
2884
  submitCropBtn.textContent = "Saving...";
2885
+
2886
+ // 1. Update Firestore
2926
2887
  await updateDoc(doc(db, "users", currentUser.uid), {
2927
2888
  customPfp: base64,
2928
2889
  pfpType: 'custom'
2929
2890
  });
2930
2891
 
2892
+ // 2. Update Local State Immediately
2931
2893
  currentUser.customPfp = base64;
2932
2894
  currentUser.pfpType = 'custom';
2933
2895
  localStorage.setItem(USER_DATA_KEY, JSON.stringify(currentUser));
2896
+ updateUIWithUser(currentUser);
2897
+ updateSettingsPfpPreview(currentUser, pfpPreview);
2934
2898
 
2935
- // Update UI
2936
- pfpPreview.innerText = "";
2937
- pfpPreview.style.backgroundImage = `url('${base64}')`;
2938
- pfpPreview.style.backgroundColor = "transparent";
2939
- pfpPreview.style.backgroundSize = "cover";
2940
-
2899
+ // Update UI Feedback
2941
2900
  cropperModal.style.display = 'none';
2942
2901
  saveUploadPfpBtn.innerText = "Saved!"; // Feedback on the main btn too
2943
2902
  } catch (e) {
@@ -3151,7 +3110,7 @@
3151
3110
  // Global store for active keys (in-memory)
3152
3111
  window.activePanicKeys = [];
3153
3112
 
3154
- async function loadPanicKeys() {
3113
+ async function loadPanicKeys() {
3155
3114
  try {
3156
3115
  const settingsMap = await getPanicKeySettings();
3157
3116
  window.activePanicKeys = []; // Reset
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "4sp-dv-latest",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "The latest HTML file of the 4SP Version 5 Client.",
5
5
  "license": "ISC",
6
6
  "author": "",