4sp-dv 1.0.30 → 1.0.32

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.29/logged-in/">
9
+ <base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.31/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
 
@@ -778,7 +778,7 @@
778
778
  const displayUsername = document.getElementById('display-username');
779
779
  const displayEmail = document.getElementById('display-email');
780
780
 
781
- const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.29/logged-in/';
781
+ const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.31/logged-in/';
782
782
 
783
783
  // Preload Logos
784
784
  const preloadImgs = [
@@ -83,9 +83,9 @@
83
83
  .btn-toolbar-style {
84
84
  background: var(--menu-bg);
85
85
  border: 1px solid var(--menu-border);
86
- border-radius: 0.75rem;
86
+ border-radius: 14px; /* MATCHES auth-btn */
87
87
  color: var(--menu-text);
88
- padding: 0.5rem 1rem;
88
+ padding: 0.5rem 1.25rem;
89
89
  font-weight: 500;
90
90
  cursor: pointer;
91
91
  transition: all 0.2s;
@@ -100,6 +100,7 @@
100
100
  background-color: var(--menu-bg);
101
101
  border-color: #fff;
102
102
  color: var(--tab-hover-text);
103
+ transform: translateY(-1px);
103
104
  }
104
105
 
105
106
  /* --- Card Action Buttons --- */
@@ -109,7 +110,7 @@
109
110
  justify-content: center;
110
111
  width: 2.25rem;
111
112
  height: 2.25rem;
112
- border-radius: 0.5rem;
113
+ border-radius: 14px; /* MATCHES auth-btn */
113
114
  background-color: transparent;
114
115
  border: 1px solid transparent;
115
116
  color: #9ca3af;
@@ -121,6 +122,7 @@
121
122
  background-color: var(--tab-active-bg);
122
123
  color: var(--tab-active-text);
123
124
  border-color: var(--tab-active-border);
125
+ transform: scale(1.1);
124
126
  }
125
127
 
126
128
  /* Favorite Button Logic */
@@ -161,31 +163,62 @@
161
163
  }
162
164
 
163
165
  /* --- Animations --- */
166
+ @keyframes fadeIn {
167
+ from { opacity: 0; transform: scale(0.98); }
168
+ to { opacity: 1; transform: scale(1); }
169
+ }
164
170
  @keyframes fadeOut {
165
171
  from { opacity: 1; transform: scale(1); }
166
- to { opacity: 0; transform: scale(0.95); }
172
+ to { opacity: 0; transform: scale(0.98); }
167
173
  }
168
- .fade-out {
174
+ .animate-fade-in { animation: fadeIn 0.3s ease-out forwards; }
175
+ .animate-fade-out { animation: fadeOut 0.3s ease-out forwards; }
176
+
177
+ .fade-out-card {
169
178
  animation: fadeOut 0.3s ease-out forwards;
170
179
  pointer-events: none;
171
180
  }
172
181
 
173
182
  /* Search Bar & Results */
183
+ #bottom-fixed-bar {
184
+ transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease;
185
+ }
186
+ #bottom-fixed-bar:focus-within {
187
+ transform: translateY(-5px);
188
+ border-color: #4f46e5;
189
+ box-shadow: 0 10px 30px rgba(0,0,0,0.8), 0 0 0 2px rgba(79, 70, 229, 0.2);
190
+ }
191
+
174
192
  .search-results-container {
175
193
  max-height: 300px;
176
194
  overflow-y: auto;
177
195
  scrollbar-width: thin;
178
196
  scrollbar-color: #333 transparent;
197
+ transition: opacity 0.3s ease;
179
198
  }
180
199
  .search-item {
181
200
  display: flex;
182
201
  cursor: pointer;
183
- transition: background-color 0.2s;
202
+ transition: all 0.2s;
184
203
  color: #e5e7eb;
185
204
  text-decoration: none;
186
205
  padding: 10px 15px;
187
206
  }
188
- .search-item:hover { background-color: rgba(255, 255, 255, 0.05); }
207
+ .search-item:hover {
208
+ background-color: rgba(255, 255, 255, 0.05);
209
+ padding-left: 20px;
210
+ }
211
+
212
+ .category-badge {
213
+ font-size: 0.6rem;
214
+ padding: 1px 6px;
215
+ border-radius: 6px;
216
+ font-weight: 400;
217
+ margin-top: 4px;
218
+ border: 1px solid currentColor;
219
+ background: transparent !important;
220
+ display: inline-block;
221
+ }
189
222
 
190
223
  @media (max-width: 768px) {
191
224
  #bottom-fixed-bar {
@@ -200,45 +233,56 @@
200
233
  .aspect-w-3 > * { position: absolute; height: 100%; width: 100%; top: 0; right: 0; bottom: 0; left: 0; }
201
234
 
202
235
  /* --- Game Viewer Modal --- */
203
- #zoneViewer { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.9); z-index: 5000; flex-direction: column; }
236
+ #zoneViewer {
237
+ display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
238
+ background-color: #000; z-index: 5000; flex-direction: column;
239
+ opacity: 0;
240
+ }
241
+ #zoneViewer.active { display: flex; opacity: 1; }
242
+
204
243
  #zoneViewer .zone-header {
205
- background-color: #111111;
206
- padding: 12px 20px;
244
+ background-color: #0a0a0a;
245
+ padding: 12px 24px;
207
246
  display: flex;
208
247
  justify-content: space-between;
209
248
  align-items: center;
210
- border-bottom: 1px solid #252525;
211
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
249
+ border-bottom: 1px solid #1a1a1a;
250
+ box-shadow: 0 2px 10px rgba(0,0,0,0.5);
212
251
  flex-shrink: 0;
213
252
  }
214
253
  #zoneViewer .zone-header .zone-title h2 {
215
254
  margin: 0;
216
- font-size: 1.3em;
255
+ font-size: 1.2rem;
217
256
  color: #ffffff;
218
257
  font-family: 'Geist', sans-serif;
258
+ font-weight: 500;
219
259
  }
220
- #zoneViewer .zone-header .zone-controls { display: flex; align-items: center; }
260
+ #zoneViewer .zone-header .zone-controls { display: flex; align-items: center; gap: 8px; }
261
+
221
262
  #zoneViewer .zone-header .zone-controls button,
222
263
  #zoneViewer .zone-header .zone-controls a {
223
- margin-left: 8px;
264
+ margin: 0;
224
265
  width: 40px;
225
266
  height: 40px;
226
- background-color: rgba(255, 255, 255, 0.1);
227
- backdrop-filter: blur(5px);
228
- -webkit-backdrop-filter: blur(5px);
229
- color: white;
230
- border: 1px solid rgba(255, 255, 255, 0.2);
231
- border-radius: 0.5rem;
267
+ background-color: #1a1a1a;
268
+ color: #d1d5db;
269
+ border: 1px solid #333;
270
+ border-radius: 14px; /* MATCHES auth-btn */
232
271
  cursor: pointer;
233
- font-size: 1em;
272
+ font-size: 1rem;
234
273
  display: flex;
235
274
  align-items: center;
236
275
  justify-content: center;
237
- transition: background-color 0.2s ease-in-out;
276
+ transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
238
277
  text-decoration: none;
239
278
  }
240
279
  #zoneViewer .zone-header .zone-controls button:hover,
241
- #zoneViewer .zone-header .zone-controls a:hover { background-color: rgba(255, 255, 255, 0.2); }
280
+ #zoneViewer .zone-header .zone-controls a:hover {
281
+ background-color: rgba(79, 70, 229, 0.1);
282
+ border-color: #4f46e5;
283
+ color: #4f46e5;
284
+ transform: scale(1.1);
285
+ }
242
286
  #zoneViewer iframe { flex-grow: 1; border: none; background-color: #000; }
243
287
  .hidden { display: none !important; }
244
288
 
@@ -710,7 +754,7 @@
710
754
  <div class="absolute inset-0 p-4 sm:p-6 flex flex-col justify-between rounded-2xl">
711
755
  <div class="flex items-start justify-between w-full">
712
756
  <h3 class="text-4xl font-bold text-white truncate drop-shadow-lg" style="max-width: 80%;" title="${game.name}">${game.name}</h3>
713
- <div class="flex items-center space-x-3 bg-black/80 backdrop-blur-md rounded-xl px-2 py-1">
757
+ <div class="flex items-center gap-2 bg-black/80 backdrop-blur-md rounded-2xl p-2">
714
758
  ${gameButtonsHtml}
715
759
  ${favoriteButtonHtml}
716
760
  </div>
@@ -765,7 +809,7 @@
765
809
  <div class="relative w-full cursor-pointer group">
766
810
  <div class="aspect-w-3 aspect-h-2"><img data-src="${imgSrc}" alt="${game.name}" class="w-full h-full object-cover"></div>
767
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>
768
- <div class="absolute bottom-2 right-2 bg-black/80 backdrop-blur-md rounded-xl px-2 py-1 flex items-center space-x-2 shadow-lg">
812
+ <div class="absolute bottom-2 right-2 bg-black/80 backdrop-blur-md rounded-2xl p-2 flex items-center gap-2 shadow-lg">
769
813
  <button class="btn-card-action play-action" title="Play Game"><i class="fa-solid fa-play transition-colors"></i></button>
770
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>
771
815
  </div>
@@ -805,7 +849,8 @@
805
849
  const zoneFrame = document.getElementById('zoneFrame');
806
850
  const zoneNameEl = document.getElementById('zoneNameEl');
807
851
  const downloadBtn = document.getElementById('downloadBtnZone');
808
-
852
+ const controls = zoneViewer.querySelector('.zone-controls');
853
+
809
854
  // Setup Download Button (Specific to Eaglercraft)
810
855
  if (game.baseGameId === 'other-eaglercraft' || game.id === 'other-eaglercraft') {
811
856
  downloadBtn.href = game.url;
@@ -818,6 +863,26 @@
818
863
  downloadBtn.removeAttribute('download');
819
864
  }
820
865
 
866
+ // --- INJECT FAVORITE BUTTON IN PLAYER HEADER ---
867
+ // Remove existing if any
868
+ const existingFav = controls.querySelector('.player-fav-btn');
869
+ if (existingFav) existingFav.remove();
870
+
871
+ const favBtn = document.createElement('button');
872
+ favBtn.className = 'player-fav-btn btn-card-action fav-action';
873
+ favBtn.innerHTML = '<i class="fa-solid fa-star fa-solid-star"></i><i class="fa-regular fa-star fa-regular-star"></i>';
874
+ const isFavorited = getFavorites().map(String).includes(String(game.id));
875
+ if (isFavorited) favBtn.classList.add('favorited');
876
+
877
+ favBtn.onclick = (e) => {
878
+ e.stopPropagation();
879
+ toggleFavorite(game.id);
880
+ favBtn.classList.toggle('favorited');
881
+ };
882
+
883
+ // Prepend to controls (Left side)
884
+ controls.prepend(favBtn);
885
+
821
886
  zoneNameEl.textContent = game.name;
822
887
 
823
888
  // RESET FRAME BEFORE LOAD
@@ -829,16 +894,16 @@
829
894
  zoneFrame.setAttribute('allow', 'fullscreen; pointer-lock; autoplay; clipboard-write');
830
895
 
831
896
  // --- REVISED LOGIC FOR LOADING GAMES ---
832
- // With raw.githack.com, we can treat GN-Math games as standard URL games again
833
- // because Githack serves correct Content-Type: text/html headers.
834
897
  const isStandardURLGame = game.category === 'StrongdogXP' || game.category === 'Others' || game.category === 'GN-Math';
835
898
 
836
899
  if (isStandardURLGame) {
837
- // Load URL directly for standard games
838
900
  zoneFrame.src = game.url;
901
+
902
+ // SHOW WITH ANIMATION
839
903
  zoneViewer.style.display = "flex";
904
+ zoneViewer.classList.remove('animate-fade-out');
905
+ zoneViewer.classList.add('active', 'animate-fade-in');
840
906
  } else {
841
- // Fallback for other potential categories (same logic as before: fetch & write)
842
907
  fetch(`${game.url}?t=${Date.now()}`)
843
908
  .then(response => {
844
909
  if (!response.ok) throw new Error(`HTTP error ${response.status}`);
@@ -849,29 +914,43 @@
849
914
  doc.open();
850
915
  doc.write(html);
851
916
  doc.close();
917
+
918
+ // SHOW WITH ANIMATION
852
919
  zoneViewer.style.display = "flex";
920
+ zoneViewer.classList.remove('animate-fade-out');
921
+ zoneViewer.classList.add('active', 'animate-fade-in');
853
922
  })
854
923
  .catch(error => {
855
924
  console.error(`Failed to load game "${game.name}": ${error.message}`);
856
925
  zoneFrame.src = game.url;
926
+
857
927
  zoneViewer.style.display = "flex";
928
+ zoneViewer.classList.remove('animate-fade-out');
929
+ zoneViewer.classList.add('active', 'animate-fade-in');
858
930
  });
859
931
  }
860
932
  }
861
933
 
862
934
  function closeZoneViewer() {
863
- updateURL(categories[currentCategoryIndex]);
864
- const downloadBtn = document.getElementById('downloadBtnZone');
865
- downloadBtn.classList.add('hidden');
866
- downloadBtn.href = '#';
867
- downloadBtn.removeAttribute('download');
868
- downloadBtn.removeAttribute('target');
869
935
  const zoneViewer = document.getElementById('zoneViewer');
870
936
  const zoneFrame = document.getElementById('zoneFrame');
871
- zoneViewer.style.display = "none";
872
- if (zoneFrame) {
873
- zoneFrame.src = 'about:blank';
874
- }
937
+
938
+ zoneViewer.classList.remove('animate-fade-in');
939
+ zoneViewer.classList.add('animate-fade-out');
940
+
941
+ setTimeout(() => {
942
+ zoneViewer.style.display = "none";
943
+ zoneViewer.classList.remove('active', 'animate-fade-out');
944
+ if (zoneFrame) {
945
+ zoneFrame.src = 'about:blank';
946
+ }
947
+ updateURL(categories[currentCategoryIndex]);
948
+ const downloadBtn = document.getElementById('downloadBtnZone');
949
+ downloadBtn.classList.add('hidden');
950
+ downloadBtn.href = '#';
951
+ downloadBtn.removeAttribute('download');
952
+ downloadBtn.removeAttribute('target');
953
+ }, 300);
875
954
  }
876
955
 
877
956
  // --- Render Lists ---
@@ -893,16 +972,10 @@
893
972
  else if (game.versions) {
894
973
  game.versions.forEach(v => {
895
974
  if (favoriteIds.includes(String(v.favoriteId))) {
896
- // Need to resolve URL here for favorites too!
897
975
  let vUrl = v.url;
898
976
  if (game.category === 'Others' || game.category === 'Gameboy Games') {
899
977
  vUrl = resolveGameUrl(v.url);
900
- } else if (game.category === 'StrongdogXP') {
901
- // Strongdog favorites usually just rely on the ID to regenerate logic, but for safety in this object:
902
- // Logic remains handled inside createGameCard via ID lookup generally, but let's leave as is
903
- // because strongdog logic inside createGameCard regenerates paths based on ID/Page props anyway.
904
978
  }
905
-
906
979
  favoriteGames.push({ ...game, id: v.favoriteId, name: `${game.name} - ${v.name}`, url: vUrl, versions: undefined });
907
980
  }
908
981
  });
@@ -980,13 +1053,19 @@
980
1053
  linkEl.dataset.gameId = game.id;
981
1054
  let isFavorite = favorites.includes(String(game.id));
982
1055
 
1056
+ const catLabel = game.category === 'StrongdogXP' ? 'StrongdogXP' : (game.category === 'GN-Math' ? 'GN-Math' : 'Others');
1057
+ const catColor = game.category === 'StrongdogXP' ? 'text-strongdog-orange' : (game.category === 'GN-Math' ? 'text-gn-math' : 'text-gray-400');
1058
+
983
1059
  linkEl.innerHTML = `
984
1060
  <div class="flex items-center truncate min-w-0">
985
1061
  <img src="${imgSrc}" alt="${game.name}" class="w-10 h-10 rounded-lg object-cover flex-shrink-0">
986
- <span class="ml-3 font-medium truncate text-gray-200" title="${game.name}">${game.name}</span>
1062
+ <div class="flex flex-col ml-3 truncate min-w-0">
1063
+ <span class="font-medium truncate text-gray-200" title="${game.name}">${game.name}</span>
1064
+ <span class="category-badge w-fit ${catColor}">${catLabel}</span>
1065
+ </div>
987
1066
  </div>
988
1067
  <div class="flex items-center flex-shrink-0">
989
- <div class="bg-black/80 backdrop-blur-md rounded-xl px-2 py-1 flex items-center space-x-2">
1068
+ <div class="bg-black/80 backdrop-blur-md rounded-2xl p-2 flex items-center gap-2">
990
1069
  <button class="btn-card-action play-action" title="Play Game">
991
1070
  <i class="fa-solid fa-play transition-colors"></i>
992
1071
  </button>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "4sp-dv",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/v5-4simpleproblems/v5-4simpleproblems-dv#readme",