4sp-dv 1.0.43 → 1.0.44

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.41/logged-in/">
9
+ <base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.43/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
 
@@ -102,7 +102,7 @@
102
102
  display: flex;
103
103
  align-items: center;
104
104
  justify-content: center;
105
- color: #ffffff;
105
+ color: var(--glide-btn-color, #ffffff);
106
106
  font-size: 1rem;
107
107
  cursor: pointer;
108
108
  opacity: 1;
@@ -181,27 +181,71 @@
181
181
 
182
182
  /* Auth Menu & Settings */
183
183
  .auth-menu {
184
- position: absolute; right: 0; top: 50px; width: 14rem;
184
+ position: absolute;
185
+ right: 0;
186
+ top: 55px;
187
+ width: 16rem;
185
188
  background: var(--menu-bg, #000);
186
- border: 1px solid var(--menu-border, #374151);
187
- border-radius: 0.75rem;
188
- padding: 0.5rem; display: none; flex-direction: column; gap: 0.25rem; z-index: 50;
189
+ border: 1px solid var(--menu-border, #333);
190
+ border-radius: 1.5rem;
191
+ padding: 0.75rem;
192
+ display: none;
193
+ flex-direction: column;
194
+ gap: 0.5rem;
195
+ z-index: 50;
196
+ box-shadow: 0 10px 30px rgba(0,0,0,0.6);
197
+ transform-origin: top right;
198
+ }
199
+
200
+ @keyframes menu-pop-in {
201
+ 0% { opacity: 0; transform: translateY(-10px) scale(0.95); }
202
+ 70% { transform: translateY(2px) scale(1.01); }
203
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
204
+ }
205
+
206
+ .auth-menu.open {
207
+ display: flex;
208
+ animation: menu-pop-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
189
209
  }
190
- .auth-menu.open { display: flex; }
191
210
 
192
211
  .auth-menu-item {
193
- display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem;
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 0.75rem;
215
+ padding: 0.75rem 1rem;
194
216
  color: var(--menu-text, #d1d5db);
195
- border-radius: 0.5rem; text-decoration: none; font-size: 0.875rem;
196
- transition: background 0.15s; cursor: pointer;
217
+ background: #0a0a0a;
218
+ border: 1px solid #333;
219
+ border-radius: 1rem;
220
+ text-decoration: none;
221
+ font-size: 0.9rem;
222
+ font-weight: 500;
223
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
224
+ cursor: pointer;
197
225
  }
226
+
198
227
  .auth-menu-item:hover {
199
- background-color: var(--menu-item-hover-bg, #374151);
200
- color: var(--menu-item-hover-text, white);
228
+ background-color: #000;
229
+ border-color: #fff;
230
+ color: #fff;
231
+ transform: translateY(-2px) scale(1.02);
232
+ box-shadow: 0 5px 15px rgba(255,255,255,0.05);
233
+ }
234
+
235
+ .auth-menu-item:active {
236
+ transform: translateY(0) scale(0.98);
237
+ }
238
+
239
+ .auth-menu-item .fa-gear {
240
+ display: inline-block;
241
+ transform-origin: center;
242
+ /* Quick transition for each "click" step */
243
+ transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
201
244
  }
245
+
202
246
  .auth-header {
203
- padding: 0.5rem;
204
- border-bottom: 1px solid var(--menu-divider, #374151);
247
+ padding: 0.5rem 1rem;
248
+ border-bottom: 1px solid var(--menu-divider, #333);
205
249
  margin-bottom: 0.25rem;
206
250
  }
207
251
  .auth-username { color: var(--menu-username-text, white); font-weight: 400; font-size: 0.9rem; }
@@ -447,6 +491,16 @@
447
491
  }
448
492
 
449
493
  /* Notifications */
494
+ #notification-container {
495
+ position: fixed;
496
+ bottom: 2rem;
497
+ right: 2rem;
498
+ display: flex;
499
+ flex-direction: column;
500
+ gap: 0.75rem;
501
+ z-index: 9999;
502
+ pointer-events: none;
503
+ }
450
504
  .notification-toast {
451
505
  background-color: #0a0a0a; border: 1px solid #333; border-radius: 14px;
452
506
  padding: 0.75rem 1.25rem; color: #fff; box-shadow: 0 4px 15px rgba(0,0,0,0.5);
@@ -520,7 +574,7 @@
520
574
  <div class="auth-username" id="display-username">Client User</div>
521
575
  <div class="auth-email" id="display-email">local@client</div>
522
576
  </div>
523
- <a href="#" class="auth-menu-item" onclick="loadPage('settings.html'); return false;">
577
+ <a href="#" id="auth-settings-btn" class="auth-menu-item" onclick="loadPage('settings.html'); return false;">
524
578
  <i class="fa-solid fa-gear w-4"></i> Settings
525
579
  </a>
526
580
  <div class="auth-menu-item text-red-400 hover:text-red-300" id="logout-btn">
@@ -778,7 +832,7 @@
778
832
  const displayUsername = document.getElementById('display-username');
779
833
  const displayEmail = document.getElementById('display-email');
780
834
 
781
- const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.41/logged-in/';
835
+ const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.43/logged-in/';
782
836
 
783
837
  // Preload Logos
784
838
  const preloadImgs = [
@@ -1069,10 +1123,11 @@
1069
1123
 
1070
1124
  let currentPath = activePageUrl.replace(BASE_URL, '');
1071
1125
 
1072
- Object.values(PAGE_DATA).forEach(page => {
1126
+ Object.entries(PAGE_DATA).forEach(([key, page]) => {
1073
1127
  const isAuth = page.url === currentPath;
1074
1128
  const tab = document.createElement('a');
1075
1129
  tab.className = `nav-tab ${isAuth ? 'active' : ''}`;
1130
+ if (key === 'settings') tab.id = 'nav-settings-tab';
1076
1131
  tab.innerHTML = `<i class="${page.icon}"></i> ${page.name}`;
1077
1132
  tab.onclick = () => loadPage(page.url);
1078
1133
  tabsContainerEl.appendChild(tab);
@@ -2182,6 +2237,10 @@
2182
2237
  // Save to local storage for persistence on reload
2183
2238
  localStorage.setItem('user-navbar-theme', JSON.stringify(theme));
2184
2239
 
2240
+ // Apply Glide Button Color Sync
2241
+ const glideColor = theme['tab-active-text'] || theme['accent-color'] || '#ffffff';
2242
+ root.style.setProperty('--glide-btn-color', glideColor);
2243
+
2185
2244
  // Also update user doc if logged in
2186
2245
  if (currentUser) {
2187
2246
  updateDoc(doc(db, "users", currentUser.uid), { navbarTheme: theme }).catch(console.error);
@@ -2518,6 +2577,92 @@
2518
2577
  osc.stop(audioCtx.currentTime + 0.015);
2519
2578
  }
2520
2579
 
2580
+ window.playMechClick = function(isReverse = false) {
2581
+ if (isMuted) return;
2582
+ if (audioCtx.state === 'suspended') audioCtx.resume();
2583
+ const now = audioCtx.currentTime;
2584
+
2585
+ const noiseGainVal = isReverse ? 0.015 : 0.02;
2586
+ const pitchFreq = isReverse ? 100 : 150;
2587
+
2588
+ // High-frequency noise burst
2589
+ const bufferSize = audioCtx.sampleRate * 0.01;
2590
+ const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
2591
+ const data = buffer.getChannelData(0);
2592
+ for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
2593
+
2594
+ const noise = audioCtx.createBufferSource();
2595
+ noise.buffer = buffer;
2596
+ const filter = audioCtx.createBiquadFilter();
2597
+ filter.type = 'highpass';
2598
+ filter.frequency.value = isReverse ? 3000 : 5000;
2599
+ const gain = audioCtx.createGain();
2600
+ gain.gain.setValueAtTime(noiseGainVal, now);
2601
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.01);
2602
+
2603
+ noise.connect(filter);
2604
+ filter.connect(gain);
2605
+ gain.connect(audioCtx.destination);
2606
+ noise.start();
2607
+ noise.stop(now + 0.01);
2608
+
2609
+ // Low-frequency "thud"
2610
+ const osc = audioCtx.createOscillator();
2611
+ const oscGain = audioCtx.createGain();
2612
+ osc.type = 'triangle';
2613
+ osc.frequency.setValueAtTime(pitchFreq, now);
2614
+ oscGain.gain.setValueAtTime(0.01, now);
2615
+ oscGain.gain.exponentialRampToValueAtTime(0.001, now + 0.02);
2616
+ osc.connect(oscGain);
2617
+ oscGain.connect(audioCtx.destination);
2618
+ osc.start();
2619
+ osc.stop(now + 0.02);
2620
+ };
2621
+
2622
+ // Gear Clicking Sequence
2623
+ let gearHoverStartTime = 0;
2624
+ let currentGearRotation = 0;
2625
+ const gearClickDelays = [0, 120, 240, 360]; // Adjusted for 0.5s total
2626
+
2627
+ const handleGearMouseOver = (e) => {
2628
+ const target = e.target.closest('#auth-settings-btn, #nav-settings-tab');
2629
+ if (target) {
2630
+ const icon = target.querySelector('.fa-gear');
2631
+ gearHoverStartTime = Date.now();
2632
+ gearClickDelays.forEach((delay, index) => {
2633
+ setTimeout(() => {
2634
+ if (target.matches(':hover')) {
2635
+ currentGearRotation = (index + 1) * 45;
2636
+ icon.style.transform = `rotate(${currentGearRotation}deg)`;
2637
+ window.playMechClick(false);
2638
+ }
2639
+ }, delay);
2640
+ });
2641
+ }
2642
+ };
2643
+
2644
+ const handleGearMouseOut = (e) => {
2645
+ const target = e.target.closest('#auth-settings-btn, #nav-settings-tab');
2646
+ if (target) {
2647
+ const icon = target.querySelector('.fa-gear');
2648
+ const elapsed = Date.now() - gearHoverStartTime;
2649
+ const clickCount = gearClickDelays.filter(d => d <= elapsed).length;
2650
+
2651
+ for (let i = 0; i < clickCount; i++) {
2652
+ setTimeout(() => {
2653
+ if (!target.matches(':hover')) {
2654
+ currentGearRotation -= 45;
2655
+ icon.style.transform = `rotate(${currentGearRotation}deg)`;
2656
+ window.playMechClick(true);
2657
+ }
2658
+ }, gearClickDelays[i]);
2659
+ }
2660
+ }
2661
+ };
2662
+
2663
+ document.addEventListener('mouseover', handleGearMouseOver);
2664
+ document.addEventListener('mouseout', handleGearMouseOut);
2665
+
2521
2666
  const notificationContainer = document.getElementById('notification-container');
2522
2667
  function showNotification(message, iconClass = 'fa-solid fa-info-circle', type = 'info') {
2523
2668
  if (!notificationContainer) return;
@@ -331,6 +331,24 @@
331
331
  .custom-scrollbar::-webkit-scrollbar-track { background: rgba(0,0,0,0.3); border-radius: 4px; }
332
332
  .custom-scrollbar::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
333
333
  .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #555; }
334
+ @keyframes popOut {
335
+ 0% { transform: scale(1); opacity: 1; }
336
+ 40% { transform: scale(1.1); opacity: 1; }
337
+ 100% { transform: scale(0); opacity: 0; }
338
+ }
339
+ .animate-pop-out { animation: popOut 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }
340
+
341
+ .particle {
342
+ position: fixed;
343
+ pointer-events: none;
344
+ border-radius: 50%;
345
+ z-index: 10000;
346
+ animation: particle-fly 0.6s ease-out forwards;
347
+ }
348
+ @keyframes particle-fly {
349
+ 0% { transform: translate(0, 0) scale(1); opacity: 1; }
350
+ 100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
351
+ }
334
352
  </style>
335
353
  </head>
336
354
 
@@ -580,25 +598,47 @@
580
598
  const getFavorites = () => JSON.parse(localStorage.getItem(FAVORITES_KEY)) || [];
581
599
  const saveFavorites = (favs) => localStorage.setItem(FAVORITES_KEY, JSON.stringify(favs));
582
600
 
601
+ function createParticles(x, y, color) {
602
+ for (let i = 0; i < 8; i++) {
603
+ const p = document.createElement('div');
604
+ p.className = 'particle';
605
+ p.style.left = x + 'px';
606
+ p.style.top = y + 'px';
607
+ p.style.width = '6px';
608
+ p.style.height = '6px';
609
+ p.style.backgroundColor = color || '#facc15';
610
+
611
+ const dx = (Math.random() - 0.5) * 100;
612
+ const dy = (Math.random() - 0.5) * 100;
613
+ p.style.setProperty('--dx', dx + 'px');
614
+ p.style.setProperty('--dy', dy + 'px');
615
+
616
+ document.body.appendChild(p);
617
+ setTimeout(() => p.remove(), 600);
618
+ }
619
+ }
620
+
583
621
  function toggleFavorite(gameId) {
584
622
  const strGameId = String(gameId);
585
623
  let favorites = getFavorites().map(String);
586
- const isFavorited = favorites.includes(strGameId);
624
+ const isRemoving = favorites.includes(strGameId);
587
625
 
588
- if (isFavorited) {
626
+ if (isRemoving) {
589
627
  favorites = favorites.filter(id => id !== strGameId);
590
628
  saveFavorites(favorites);
591
629
  updateAllFavoriteButtons();
592
- const favCard = favoritesGameList.querySelector(`.zone-item[data-game-id='${strGameId}'], .other-zone-item[data-game-id='${strGameId}']`);
630
+ const favCard = favoritesGameList.querySelector(`[data-game-id='${strGameId}']`);
593
631
  if (favCard) {
594
- favCard.classList.add('fade-out');
632
+ const rect = favCard.getBoundingClientRect();
633
+ createParticles(rect.left + rect.width / 2, rect.top + rect.height / 2, '#facc15');
634
+ favCard.classList.add('animate-pop-out');
595
635
  setTimeout(() => {
596
636
  favCard.remove();
597
637
  if (favoritesGameList.children.length === 0) {
598
638
  favoritesHeader.style.display = 'none';
599
639
  favoritesGameList.style.display = 'none';
600
640
  }
601
- }, 300);
641
+ }, 400);
602
642
  }
603
643
  } else {
604
644
  favorites.push(strGameId);
@@ -610,7 +650,7 @@
610
650
  if (g.versions) {
611
651
  const ver = g.versions.find(v => String(v.favoriteId) === strGameId);
612
652
  if (ver) {
613
- gameData = { ...g, id: ver.favoriteId, name: `${g.name} - ${ver.name}`, url: ver.url, versions: undefined };
653
+ gameData = { ...g, id: ver.favoriteId, name: `${game.name} - ${ver.name}`, url: ver.url, versions: undefined };
614
654
  return true;
615
655
  }
616
656
  }
@@ -620,9 +660,7 @@
620
660
  if (gameData) {
621
661
  favoritesHeader.style.display = "block";
622
662
  favoritesGameList.style.display = "grid";
623
- favoritesGameList.classList.add('grid', 'grid-cols-2', 'sm:grid-cols-3', 'md:grid-cols-5', 'lg:grid-cols-7', 'gap-4');
624
- const newCard = createGameCard(gameData, true);
625
- favoritesGameList.appendChild(newCard);
663
+ favoritesGameList.appendChild(createGameCard(gameData, true));
626
664
  }
627
665
  }
628
666
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "4sp-dv",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/v5-4simpleproblems/v5-4simpleproblems-dv#readme",