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.
- package/4simpleproblems_v5.html +162 -17
- package/logged-in/games.html +47 -9
- package/package.json +1 -1
package/4simpleproblems_v5.html
CHANGED
|
@@ -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.
|
|
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;
|
|
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, #
|
|
187
|
-
border-radius:
|
|
188
|
-
padding: 0.
|
|
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;
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: 0.75rem;
|
|
215
|
+
padding: 0.75rem 1rem;
|
|
194
216
|
color: var(--menu-text, #d1d5db);
|
|
195
|
-
|
|
196
|
-
|
|
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:
|
|
200
|
-
color:
|
|
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, #
|
|
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.
|
|
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.
|
|
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;
|
package/logged-in/games.html
CHANGED
|
@@ -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
|
|
624
|
+
const isRemoving = favorites.includes(strGameId);
|
|
587
625
|
|
|
588
|
-
if (
|
|
626
|
+
if (isRemoving) {
|
|
589
627
|
favorites = favorites.filter(id => id !== strGameId);
|
|
590
628
|
saveFavorites(favorites);
|
|
591
629
|
updateAllFavoriteButtons();
|
|
592
|
-
const favCard = favoritesGameList.querySelector(
|
|
630
|
+
const favCard = favoritesGameList.querySelector(`[data-game-id='${strGameId}']`);
|
|
593
631
|
if (favCard) {
|
|
594
|
-
favCard.
|
|
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
|
-
},
|
|
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: `${
|
|
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.
|
|
624
|
-
const newCard = createGameCard(gameData, true);
|
|
625
|
-
favoritesGameList.appendChild(newCard);
|
|
663
|
+
favoritesGameList.appendChild(createGameCard(gameData, true));
|
|
626
664
|
}
|
|
627
665
|
}
|
|
628
666
|
}
|