4sp-dv 1.0.42 → 1.0.43

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.
@@ -1189,8 +1189,7 @@
1189
1189
  pfpLetterBg: data.pfpLetterBg,
1190
1190
  letterAvatarText: data.letterAvatarText,
1191
1191
  letterAvatarTextColor: data.letterAvatarTextColor, // New Field
1192
- navbarTheme: data.navbarTheme, // Sync theme if updated remotely
1193
- verified_online: true // Mark as verified for offline use
1192
+ navbarTheme: data.navbarTheme // Sync theme if updated remotely
1194
1193
  };
1195
1194
 
1196
1195
  localStorage.setItem(USER_DATA_KEY, JSON.stringify(newUserData));
@@ -1281,26 +1280,6 @@
1281
1280
  unlockBtn.classList.add('hidden');
1282
1281
  codeInput.classList.add('hidden');
1283
1282
 
1284
- // --- OFFLINE CHECK ---
1285
- if (!navigator.onLine) {
1286
- console.log("Client is offline. Checking for bypass...");
1287
- let cachedUser = null;
1288
- try { cachedUser = JSON.parse(localStorage.getItem(USER_DATA_KEY)); } catch(e){}
1289
-
1290
- if (cachedUser && cachedUser.verified_online) {
1291
- console.log("Offline bypass granted for verified user.");
1292
- currentUser = cachedUser;
1293
- updateUIWithUser(cachedUser);
1294
- const indicator = document.getElementById('offline-indicator');
1295
- if (indicator) indicator.classList.add('show');
1296
- launchApp();
1297
- return;
1298
- } else {
1299
- resetLockScreen("You are offline. Please connect to the internet to verify your code.");
1300
- return;
1301
- }
1302
- }
1303
-
1304
1283
  try {
1305
1284
  const docRef = doc(db, "access_codes", savedCode);
1306
1285
  const docSnap = await getDoc(docRef);
@@ -1332,18 +1311,7 @@
1332
1311
  }
1333
1312
  } catch(e) {
1334
1313
  console.error("Auto-login error:", e);
1335
- // Check if we can fallback to offline even on error (e.g. timeout)
1336
- let cachedUser = null;
1337
- try { cachedUser = JSON.parse(localStorage.getItem(USER_DATA_KEY)); } catch(err){}
1338
- if (cachedUser && cachedUser.verified_online) {
1339
- currentUser = cachedUser;
1340
- updateUIWithUser(cachedUser);
1341
- const indicator = document.getElementById('offline-indicator');
1342
- if (indicator) indicator.classList.add('show');
1343
- launchApp();
1344
- } else {
1345
- resetLockScreen("Connection failed. Retrying...");
1346
- }
1314
+ resetLockScreen("Connection failed. Retrying...");
1347
1315
  }
1348
1316
  }
1349
1317
  }
@@ -709,71 +709,10 @@
709
709
  { id: "other-eaglercraft", name: "Eaglercraft", imgSrc: "./images/eaglercraft.png", description: "The classic survival and building game with authentic block-based physics and multiplayer support, accessible directly in the browser.", category: "Others", versions: [ { name: "Release 1.12.2", url: "../EAGLERCRAFT/eaglercraft-1.12.2.html", favoriteId: "other-eaglercraft_1.12.2" }, { name: "Release 1.12.2 WASM", url: "../EAGLERCRAFT/eaglercraft-1.12.2-wasm.html", favoriteId: "other-eaglercraft_wasm", wasm: true } ] },
710
710
  { id: "other-gta", name: "Grand Theft Auto", imgSrc: "./images/gta.png", description: "The original top-down classic. Cause chaos, complete missions, or just go for a drive in three iconic cities.", category: "Others", versions: [ { name: "Carnage3D", url: "../CARNAGE3D/web/carnage3D.html", favoriteId: "other-gta_carnage3d" }, { name: "Grand Theft Auto (JS-DOS)", url: "../GTA-JSDOS/index.html", favoriteId: "other-gta_jsdos" } ] },
711
711
  { id: "other-simcity", name: "Sim City", imgSrc: "./images/simcity2000.png", description: "The original city-building classics. Design, build, and manage the city of your dreams in either the original Sim City or the detailed Sim City 2000.", category: "Others", versions: [ { name: "Sim City", url: "../SIM-CITY/index.html", favoriteId: "other-simcity_1" }, { name: "Sim City 2000", url: "../SIM-CITY-2/index.html", favoriteId: "other-simcity_2000" } ] },
712
- { id: "other-doom", name: "DOOM", imgSrc: "./images/doom.png", description: "The legendary first-person shooter that pioneered the genre, focusing on blasting through hordes of demons in a timeless, adrenaline-fueled classic.", category: "Others", url: "../DOOM/index.html" }
712
+ { id: "other-doom", name: "DOOM", imgSrc: "./images/doom.png", description: "The legendary first-person shooter that pioneered the genre, focusing on blasting through hordes of demons in a timeless, adrenaline-fueled classic.", category: "Others", url: "../DOOM/index.html" },
713
+ { id: "other-sm64", name: "Super Mario 64", imgSrc: "./images/sm64.png", description: "The iconic 3D platformer that revolutionized gaming. Run, jump, and triple-jump your way through vast worlds to collect stars and save the princess.", category: "Others", url: "../GAMES/sm64/index.html" }
713
714
  ];
714
715
 
715
- // --- Offline Mode Logic (IndexedDB) ---
716
- const DB_NAME = '4SP_OfflineGames';
717
- const DB_VERSION = 1;
718
- const STORE_NAME = 'games';
719
-
720
- function openDB() {
721
- return new Promise((resolve, reject) => {
722
- const request = indexedDB.open(DB_NAME, DB_VERSION);
723
- request.onupgradeneeded = (e) => {
724
- const db = e.target.result;
725
- if (!db.objectStoreNames.contains(STORE_NAME)) {
726
- db.createObjectStore(STORE_NAME, { keyPath: 'id' });
727
- }
728
- };
729
- request.onsuccess = (e) => resolve(e.target.result);
730
- request.onerror = (e) => reject(e.target.error);
731
- });
732
- }
733
-
734
- async function saveGameOffline(gameId, name, url) {
735
- try {
736
- const res = await fetch(url);
737
- if (!res.ok) throw new Error('Fetch failed');
738
- const html = await res.text();
739
- const db = await openDB();
740
- return new Promise((resolve, reject) => {
741
- const tx = db.transaction(STORE_NAME, 'readwrite');
742
- const store = tx.objectStore(STORE_NAME);
743
- store.put({ id: String(gameId), name, html, timestamp: Date.now() });
744
- tx.oncomplete = () => { db.close(); resolve(); };
745
- tx.onerror = () => reject(tx.error);
746
- });
747
- } catch (e) { console.error("Offline Save Error:", e); throw e; }
748
- }
749
-
750
- async function getOfflineGame(gameId) {
751
- const db = await openDB();
752
- return new Promise((resolve, reject) => {
753
- const tx = db.transaction(STORE_NAME, 'readonly');
754
- const store = tx.objectStore(STORE_NAME);
755
- const request = store.get(String(gameId));
756
- request.onsuccess = () => { db.close(); resolve(request.result); };
757
- request.onerror = () => reject(request.error);
758
- });
759
- }
760
-
761
- async function removeOfflineGame(gameId) {
762
- const db = await openDB();
763
- return new Promise((resolve, reject) => {
764
- const tx = db.transaction(STORE_NAME, 'readwrite');
765
- const store = tx.objectStore(STORE_NAME);
766
- store.delete(String(gameId));
767
- tx.oncomplete = () => { db.close(); resolve(); };
768
- tx.onerror = () => reject(tx.error);
769
- });
770
- }
771
-
772
- async function isGameOffline(gameId) {
773
- const game = await getOfflineGame(gameId);
774
- return !!game;
775
- }
776
-
777
716
  // --- Card Creation ---
778
717
 
779
718
  function createOthersGameCard(game) {
@@ -851,7 +790,7 @@
851
790
 
852
791
  function createGameCard(game, forceSmallCard = false) {
853
792
  if (game.category === 'Others' && !forceSmallCard) return createOthersGameCard(game);
854
- const isStrongdog = game.category === 'StrongdogXP', isGNMath = game.category === 'GN-Math';
793
+ const isStrongdog = game.category === 'StrongdogXP';
855
794
 
856
795
  // Resolve Image Src
857
796
  let imgSrc = game.imgSrc;
@@ -866,25 +805,12 @@
866
805
  const card = document.createElement("div");
867
806
  card.className = 'zone-item bg-card-dark rounded-2xl border border-brand-border overflow-hidden';
868
807
  card.dataset.gameId = game.id;
869
-
870
- let gUrl = game.url;
871
- if (game.category === 'Others' || game.category === 'Gameboy Games') {
872
- gUrl = resolveGameUrl(game.url);
873
- } else if (isStrongdog) {
874
- const baseURL = sd_getBaseURLForPage(game.page);
875
- gUrl = baseURL.endsWith("/") ? baseURL + game.href.replace(/^\.\//, "") : baseURL + "/" + game.href.replace(/^\.\//, "");
876
- }
877
-
878
- // ONLY GN-MATH GETS OFFLINE SAVING
879
- const offlineBtn = isGNMath ? `<button class="btn-card-action offline-action" data-game-id="${game.id}" data-url="${gUrl}" data-name="${game.name}" title="Enable Offline Mode"><i class="fa-solid fa-cloud"></i></button>` : '';
880
-
881
808
  card.innerHTML = `
882
809
  <div class="relative w-full cursor-pointer group">
883
810
  <div class="aspect-w-3 aspect-h-2"><img data-src="${imgSrc}" alt="${game.name}" class="w-full h-full object-cover"></div>
884
811
  <h3 class="absolute top-2 right-2 z-10 max-w-[80%] bg-black/60 backdrop-blur-md rounded-xl px-3 py-1.5 text-white truncate text-sm font-semibold shadow-lg" title="${game.name}">${game.name}</h3>
885
812
  <div class="absolute bottom-2 right-2 bg-black/50 backdrop-blur-sm rounded-2xl p-1.5 flex items-center gap-1.5 shadow-lg">
886
- <button class="btn-card-action play-action" title="Play Game"><i class="fa-solid fa-play"></i></button>
887
- ${offlineBtn}
813
+ <button class="btn-card-action play-action" title="Play Game"><i class="fa-solid fa-play transition-colors"></i></button>
888
814
  <button class="btn-card-action fav-action ${isFavorite ? 'favorited' : ''}" title="${isFavorite ? 'Remove from Favorites' : 'Add to Favorites'}"><i class="fa-solid fa-star fa-solid-star transition-colors"></i><i class="fa-regular fa-star fa-regular-star transition-colors"></i></button>
889
815
  </div>
890
816
  </div>
@@ -898,6 +824,11 @@
898
824
  const embedPath = await sd_getEmbedPath(adjustedHref, game.href, game.page);
899
825
  openZone({ name: game.name, url: embedPath, category: game.category, id: game.id });
900
826
  } else {
827
+ // For GN-Math, url is absolute (handled in init). For Others/Gameboy, we might need to resolve
828
+ let gUrl = game.url;
829
+ if (game.category === 'Others' || game.category === 'Gameboy Games') {
830
+ gUrl = resolveGameUrl(game.url);
831
+ }
901
832
  openZone({ ...game, url: gUrl });
902
833
  }
903
834
  };
@@ -905,12 +836,6 @@
905
836
  card.querySelector('.group').addEventListener('click', handlePlay);
906
837
  card.querySelector('.play-action').addEventListener('click', handlePlay);
907
838
  card.querySelector('.fav-action').addEventListener('click', (e) => { e.stopPropagation(); toggleFavorite(game.id); });
908
-
909
- if (isGNMath) {
910
- const offBtn = card.querySelector('.offline-action');
911
- offBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleOfflineMode(game.id, game.name, gUrl, offBtn); });
912
- }
913
-
914
839
  imageObserver.observe(card.querySelector('img'));
915
840
  return card;
916
841
  }
@@ -930,7 +855,7 @@
930
855
  const show404 = (errorMsg = "Game content unavailable.") => {
931
856
  const doc = zoneFrame.contentWindow.document;
932
857
  doc.open();
933
- doc.write(\`
858
+ doc.write(`
934
859
  <!DOCTYPE html>
935
860
  <html>
936
861
  <head>
@@ -945,11 +870,11 @@
945
870
  </head>
946
871
  <body>
947
872
  <h1>404</h1>
948
- <p><strong>\${game.name}</strong> could not be loaded.<br><span style="font-size: 0.9rem; opacity: 0.7;">\${errorMsg}</span></p>
873
+ <p><strong>${game.name}</strong> could not be loaded.<br><span style="font-size: 0.9rem; opacity: 0.7;">${errorMsg}</span></p>
949
874
  <div class="btn" onclick="window.parent.closeZoneViewer()">Return to Hub</div>
950
875
  </body>
951
876
  </html>
952
- \`);
877
+ `);
953
878
  doc.close();
954
879
  zoneViewer.style.display = "flex";
955
880
  zoneViewer.classList.add('active', 'animate-fade-in');
@@ -959,12 +884,16 @@
959
884
  if (game.baseGameId === 'other-eaglercraft' || game.id === 'other-eaglercraft') {
960
885
  downloadBtn.href = game.url;
961
886
  downloadBtn.download = game.name.replace(/ /g, '_') + '.html';
887
+ downloadBtn.removeAttribute('target');
962
888
  downloadBtn.classList.remove('hidden');
963
889
  } else {
964
890
  downloadBtn.classList.add('hidden');
891
+ downloadBtn.href = '#';
892
+ downloadBtn.removeAttribute('download');
965
893
  }
966
894
 
967
895
  // --- INJECT FAVORITE BUTTON IN PLAYER HEADER ---
896
+ // Remove existing if any
968
897
  const existingFav = controls.querySelector('.player-fav-btn');
969
898
  if (existingFav) existingFav.remove();
970
899
 
@@ -979,28 +908,21 @@
979
908
  toggleFavorite(game.id);
980
909
  favBtn.classList.toggle('favorited');
981
910
  };
911
+
912
+ // Prepend to controls (Left side)
982
913
  controls.prepend(favBtn);
983
914
 
984
915
  zoneNameEl.textContent = game.name;
916
+
917
+ // RESET FRAME BEFORE LOAD
985
918
  zoneFrame.src = 'about:blank';
986
919
 
920
+ // SECURITY: Set sandbox permissions
987
921
  const sandboxRules = 'allow-scripts allow-same-origin allow-forms allow-pointer-lock';
988
922
  zoneFrame.setAttribute('sandbox', sandboxRules);
989
923
  zoneFrame.setAttribute('allow', 'fullscreen; pointer-lock; autoplay; clipboard-write');
990
924
 
991
- // --- CHECK FOR OFFLINE VERSION FIRST ---
992
- const offlineGame = await getOfflineGame(game.id);
993
- if (offlineGame) {
994
- console.log("Loading offline version of", game.name);
995
- const doc = zoneFrame.contentWindow.document;
996
- doc.open();
997
- doc.write(offlineGame.html);
998
- doc.close();
999
- zoneViewer.style.display = "flex";
1000
- zoneViewer.classList.add('active', 'animate-fade-in');
1001
- return;
1002
- }
1003
-
925
+ // --- REVISED LOGIC FOR LOADING GAMES ---
1004
926
  const isStandardURLGame = game.category === 'StrongdogXP' || game.category === 'Others' || game.category === 'GN-Math';
1005
927
 
1006
928
  if (isStandardURLGame) {
@@ -1009,7 +931,10 @@
1009
931
  const check = await fetch(game.url, { method: 'HEAD' });
1010
932
  if (!check.ok) throw new Error("File not found");
1011
933
  zoneFrame.src = game.url;
934
+
935
+ // SHOW WITH ANIMATION
1012
936
  zoneViewer.style.display = "flex";
937
+ zoneViewer.classList.remove('animate-fade-out');
1013
938
  zoneViewer.classList.add('active', 'animate-fade-in');
1014
939
  } catch (e) {
1015
940
  show404("The game source is broken or blocked.");
@@ -1025,46 +950,18 @@
1025
950
  doc.open();
1026
951
  doc.write(html);
1027
952
  doc.close();
953
+
954
+ // SHOW WITH ANIMATION
1028
955
  zoneViewer.style.display = "flex";
956
+ zoneViewer.classList.remove('animate-fade-out');
1029
957
  zoneViewer.classList.add('active', 'animate-fade-in');
1030
958
  })
1031
959
  .catch(error => {
1032
- console.error(`Failed to load game "${game.name}": \${error.message}`);
1033
- zoneFrame.src = game.url;
1034
- zoneViewer.style.display = "flex";
1035
- zoneViewer.classList.add('active', 'animate-fade-in');
960
+ show404(error.message);
1036
961
  });
1037
962
  }
1038
963
  }
1039
964
 
1040
- async function toggleOfflineMode(gameId, name, url, buttonElement) {
1041
- const isSaved = await isGameOffline(gameId);
1042
- try {
1043
- if (isSaved) {
1044
- await removeOfflineGame(gameId);
1045
- showNotification(\`\${name} removed from storage.\`, 'fa-solid fa-trash', 'info');
1046
- } else {
1047
- buttonElement.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
1048
- await saveGameOffline(gameId, name, url);
1049
- showNotification(\`\${name} saved for offline play!\`, 'fa-solid fa-check-circle', 'success');
1050
- }
1051
- updateAllOfflineButtons();
1052
- } catch (e) {
1053
- showNotification(\`Failed to save \${name}.\`, 'fa-solid fa-circle-exclamation', 'warning');
1054
- }
1055
- }
1056
-
1057
- async function updateAllOfflineButtons() {
1058
- const btns = document.querySelectorAll('.offline-action');
1059
- for (const btn of btns) {
1060
- const gameId = btn.dataset.gameId;
1061
- const isSaved = await isGameOffline(gameId);
1062
- btn.classList.toggle('favorited', isSaved); // reuse favorited color
1063
- btn.innerHTML = isSaved ? '<i class="fa-solid fa-cloud-arrow-down text-green-400"></i>' : '<i class="fa-solid fa-cloud"></i>';
1064
- btn.title = isSaved ? 'Disable Offline Mode' : 'Enable Offline Mode';
1065
- }
1066
- }
1067
-
1068
965
  function closeZoneViewer() {
1069
966
  const zoneViewer = document.getElementById('zoneViewer');
1070
967
  const zoneFrame = document.getElementById('zoneFrame');
@@ -1330,7 +1227,6 @@
1330
1227
 
1331
1228
  switchCategory(currentCategoryIndex);
1332
1229
  renderFavorites();
1333
- updateAllOfflineButtons();
1334
1230
  setupInstructionOverlay();
1335
1231
 
1336
1232
  searchInput.placeholder = `Search all games...`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "4sp-dv",
3
- "version": "1.0.42",
3
+ "version": "1.0.43",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/v5-4simpleproblems/v5-4simpleproblems-dv#readme",