reaper-arcade-hub 1.0.0 → 1.0.1
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/css/styles.css +99 -9
- package/index.html +74 -3
- package/js/app.js +127 -0
- package/js/security-shield.js +39 -0
- package/js/visuals.js +171 -14
- package/main/css/styles.css +99 -9
- package/main/index.html +74 -3
- package/main/js/app.js +127 -0
- package/main/js/security-shield.js +39 -0
- package/main/js/visuals.js +171 -14
- package/package.json +1 -1
- package/verify/css/styles.css +99 -9
- package/verify/js/security-shield.js +39 -0
- package/verify/js/visuals.js +171 -14
package/css/styles.css
CHANGED
|
@@ -452,20 +452,37 @@ body {
|
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
.game-card {
|
|
455
|
-
background:
|
|
456
|
-
border:
|
|
455
|
+
background: linear-gradient(145deg, rgba(8, 18, 36, 0.95) 0%, rgba(4, 10, 22, 0.98) 100%);
|
|
456
|
+
border: 1.5px solid var(--blue-border);
|
|
457
457
|
border-radius: var(--radius-md);
|
|
458
458
|
overflow: hidden;
|
|
459
459
|
cursor: pointer;
|
|
460
|
-
transition:
|
|
460
|
+
transition: transform 0.15s cubic-bezier(0.2, 0, 0.2, 1), box-shadow 0.25s ease, border-color 0.25s ease;
|
|
461
461
|
display: flex;
|
|
462
462
|
flex-direction: column;
|
|
463
|
+
position: relative;
|
|
464
|
+
transform-style: preserve-3d;
|
|
465
|
+
will-change: transform;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.game-card::after {
|
|
469
|
+
content: '';
|
|
470
|
+
position: absolute;
|
|
471
|
+
inset: 0;
|
|
472
|
+
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(0, 210, 255, 0.25) 0%, transparent 65%);
|
|
473
|
+
opacity: 0;
|
|
474
|
+
transition: opacity 0.3s ease;
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
mix-blend-mode: screen;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.game-card:hover::after {
|
|
480
|
+
opacity: 1;
|
|
463
481
|
}
|
|
464
482
|
|
|
465
483
|
.game-card:hover {
|
|
466
|
-
transform: translateY(-4px);
|
|
467
484
|
border-color: var(--blue-bright);
|
|
468
|
-
box-shadow: 0
|
|
485
|
+
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.8), 0 0 25px rgba(0, 136, 255, 0.45);
|
|
469
486
|
}
|
|
470
487
|
|
|
471
488
|
.game-thumb-container {
|
|
@@ -971,10 +988,83 @@ body {
|
|
|
971
988
|
border: 1px solid var(--blue-bright);
|
|
972
989
|
color: #fff;
|
|
973
990
|
padding: 0.65rem 1.4rem;
|
|
974
|
-
border-radius:
|
|
975
|
-
font-size: 0.
|
|
991
|
+
border-radius: 24px;
|
|
992
|
+
font-size: 0.85rem;
|
|
976
993
|
font-weight: 700;
|
|
977
|
-
box-shadow: 0
|
|
978
|
-
z-index:
|
|
994
|
+
box-shadow: 0 0 25px var(--blue-glow);
|
|
995
|
+
z-index: 99999;
|
|
979
996
|
display: none;
|
|
997
|
+
animation: toastPop 0.3s ease;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/* Fluid Tab Slide & Fade */
|
|
1001
|
+
.tab-section {
|
|
1002
|
+
animation: tabSlideFade 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
@keyframes tabSlideFade {
|
|
1006
|
+
from {
|
|
1007
|
+
opacity: 0;
|
|
1008
|
+
transform: translateY(10px) scale(0.99);
|
|
1009
|
+
}
|
|
1010
|
+
to {
|
|
1011
|
+
opacity: 1;
|
|
1012
|
+
transform: translateY(0) scale(1);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/* Recently Played Carousel Grid */
|
|
1017
|
+
.recent-bar-container {
|
|
1018
|
+
width: 100%;
|
|
1019
|
+
max-width: 680px;
|
|
1020
|
+
margin-bottom: 1.5rem;
|
|
1021
|
+
background: rgba(4, 12, 24, 0.85);
|
|
1022
|
+
border: 1.5px solid var(--blue-border);
|
|
1023
|
+
border-radius: var(--radius-md);
|
|
1024
|
+
padding: 0.85rem 1.15rem;
|
|
1025
|
+
box-shadow: 0 0 20px rgba(0, 136, 255, 0.15);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
.recent-grid {
|
|
1029
|
+
display: grid;
|
|
1030
|
+
grid-template-columns: repeat(auto-fill, minmax(95px, 1fr));
|
|
1031
|
+
gap: 0.65rem;
|
|
1032
|
+
margin-top: 0.65rem;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.recent-card {
|
|
1036
|
+
background: #020712;
|
|
1037
|
+
border: 1px solid var(--blue-border);
|
|
1038
|
+
border-radius: 6px;
|
|
1039
|
+
padding: 0.4rem;
|
|
1040
|
+
text-align: center;
|
|
1041
|
+
cursor: pointer;
|
|
1042
|
+
transition: all 0.2s cubic-bezier(0.2, 0, 0.2, 1);
|
|
1043
|
+
display: flex;
|
|
1044
|
+
flex-direction: column;
|
|
1045
|
+
align-items: center;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.recent-card:hover {
|
|
1049
|
+
border-color: var(--blue-bright);
|
|
1050
|
+
transform: translateY(-3px) scale(1.04);
|
|
1051
|
+
box-shadow: 0 6px 18px rgba(0, 136, 255, 0.45);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.recent-card-img {
|
|
1055
|
+
width: 100%;
|
|
1056
|
+
aspect-ratio: 16/10;
|
|
1057
|
+
object-fit: cover;
|
|
1058
|
+
border-radius: 4px;
|
|
1059
|
+
margin-bottom: 0.3rem;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.recent-card-name {
|
|
1063
|
+
font-size: 0.68rem;
|
|
1064
|
+
font-weight: 700;
|
|
1065
|
+
color: #fff;
|
|
1066
|
+
white-space: nowrap;
|
|
1067
|
+
overflow: hidden;
|
|
1068
|
+
text-overflow: ellipsis;
|
|
1069
|
+
width: 100%;
|
|
980
1070
|
}
|
package/index.html
CHANGED
|
@@ -112,6 +112,14 @@
|
|
|
112
112
|
</nav>
|
|
113
113
|
|
|
114
114
|
<div class="header-right">
|
|
115
|
+
<button class="btn-stealth" id="btn-sfx-toggle" onclick="app.toggleSoundFX()" title="Toggle Sci-Fi SFX">
|
|
116
|
+
🔊 SFX
|
|
117
|
+
</button>
|
|
118
|
+
|
|
119
|
+
<button class="btn-stealth" onclick="if(window.reaperShield) window.reaperShield.togglePanicDisguise()" title="Emergency Panic Cloak (Hotkey: ~)">
|
|
120
|
+
🚨 Panic (~)
|
|
121
|
+
</button>
|
|
122
|
+
|
|
115
123
|
<button class="btn-stealth" onclick="app.openUpdateLogsModal()" title="View update logs">
|
|
116
124
|
Updates
|
|
117
125
|
</button>
|
|
@@ -140,7 +148,16 @@
|
|
|
140
148
|
<section id="section-home" class="tab-section">
|
|
141
149
|
<div class="opium-home-container">
|
|
142
150
|
<h1 class="opium-logo-title">reaper</h1>
|
|
143
|
-
<div class="opium-subtitle">788 High-Speed Unblocked Games & Live Leaderboard</div>
|
|
151
|
+
<div class="opium-subtitle">788 High-Speed Unblocked Games & Live Leaderboard</div>
|
|
152
|
+
|
|
153
|
+
<!-- Recently Played Bar -->
|
|
154
|
+
<div id="recent-games-container" class="recent-bar-container" style="display: none;">
|
|
155
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
156
|
+
<span style="font-size: 0.78rem; font-weight: 800; color: #00d2ff; letter-spacing: 0.05em; text-transform: uppercase;">⚡ RECENTLY PLAYED</span>
|
|
157
|
+
<button onclick="app.clearRecentlyPlayed()" class="btn-stealth" style="font-size: 0.68rem; padding: 0.2rem 0.5rem;">CLEAR</button>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="recent-grid" id="recent-games-grid"></div>
|
|
160
|
+
</div>
|
|
144
161
|
|
|
145
162
|
<!-- Pill Search Bar -->
|
|
146
163
|
<div class="opium-search-wrapper">
|
|
@@ -148,14 +165,20 @@
|
|
|
148
165
|
<input type="text" id="opium-search-input" class="opium-search-input" placeholder="Search 780+ games or topics..." autocomplete="off" onkeydown="if(event.key==='Enter') app.handleOpiumSearch()">
|
|
149
166
|
</div>
|
|
150
167
|
|
|
151
|
-
<!-- Quick Navigation Tiles -->
|
|
152
|
-
<div style="display: flex; gap: 0.85rem; flex-wrap: wrap; justify-content: center; max-width:
|
|
168
|
+
<!-- Quick Navigation Tiles & Save Manager -->
|
|
169
|
+
<div style="display: flex; gap: 0.85rem; flex-wrap: wrap; justify-content: center; max-width: 650px; margin-bottom: 1.25rem;">
|
|
153
170
|
<button class="btn-primary" style="padding: 0.75rem 1.4rem; font-size: 0.88rem;" onclick="app.switchTab('games')">
|
|
154
171
|
BROWSE 788 GAMES
|
|
155
172
|
</button>
|
|
156
173
|
<button class="btn-stealth" style="padding: 0.75rem 1.4rem; font-size: 0.88rem;" onclick="app.switchTab('leaderboard')">
|
|
157
174
|
PLAYTIME LEADERBOARD
|
|
158
175
|
</button>
|
|
176
|
+
<button class="btn-stealth" style="padding: 0.75rem 1.4rem; font-size: 0.88rem; color: #00ffaa; border-color: rgba(0,255,170,0.3);" onclick="app.exportSaveData()" title="Backup your game saves">
|
|
177
|
+
💾 BACKUP SAVES
|
|
178
|
+
</button>
|
|
179
|
+
<button class="btn-stealth" style="padding: 0.75rem 1.4rem; font-size: 0.88rem; color: #ffd700; border-color: rgba(255,215,0,0.3);" onclick="app.importSaveData()" title="Restore game save data">
|
|
180
|
+
📥 RESTORE SAVES
|
|
181
|
+
</button>
|
|
159
182
|
</div>
|
|
160
183
|
</div>
|
|
161
184
|
</section>
|
|
@@ -534,6 +557,54 @@
|
|
|
534
557
|
</div>
|
|
535
558
|
</div>
|
|
536
559
|
|
|
560
|
+
<!-- Emergency Google Classroom Panic Disguise Overlay -->
|
|
561
|
+
<div id="panic-disguise-overlay" style="position: fixed; inset: 0; width: 100vw; height: 100vh; background: #f8f9fa; color: #3c4043; font-family: 'Roboto', -apple-system, BlinkMacSystemFont, Arial, sans-serif; z-index: 999999; display: none; overflow-y: auto;">
|
|
562
|
+
<!-- Classroom Top Bar -->
|
|
563
|
+
<div style="height: 64px; background: #ffffff; border-bottom: 1px solid #dadce0; display: flex; align-items: center; justify-content: space-between; padding: 0 1.5rem;">
|
|
564
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
565
|
+
<span style="font-size: 1.5rem; cursor: pointer; color: #5f6368;">☰</span>
|
|
566
|
+
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
567
|
+
<img src="https://ssl.gstatic.com/classroom/favicon.png" width="24" height="24" alt="Google Classroom">
|
|
568
|
+
<span style="font-size: 1.25rem; font-weight: 500; color: #3c4043;">Google Classroom</span>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
<div style="display: flex; align-items: center; gap: 1rem;">
|
|
572
|
+
<span style="font-size: 0.85rem; color: #5f6368;">Period 4 • Mathematics</span>
|
|
573
|
+
<button onclick="if(window.reaperShield) window.reaperShield.togglePanicDisguise()" style="background: #e8f0fe; border: 1px solid #d2e3fc; color: #1a73e8; border-radius: 4px; padding: 0.35rem 0.75rem; font-size: 0.75rem; font-weight: 600; cursor: pointer;">Exit Disguise (Press ~)</button>
|
|
574
|
+
<div style="width: 32px; height: 32px; border-radius: 50%; background: #1a73e8; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 600;">S</div>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
<!-- Classroom Stream -->
|
|
579
|
+
<div style="max-width: 1000px; margin: 2rem auto; padding: 0 1.5rem;">
|
|
580
|
+
<div style="height: 180px; background: linear-gradient(135deg, #1e8e3e, #137333); border-radius: 8px; padding: 1.5rem; color: #fff; display: flex; flex-direction: column; justify-content: flex-end; margin-bottom: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.12);">
|
|
581
|
+
<h1 style="font-size: 1.8rem; margin: 0;">AP Calculus & Analytical Geometry</h1>
|
|
582
|
+
<p style="margin: 0.25rem 0 0 0; opacity: 0.9; font-size: 0.95rem;">Instructor: Dr. Harrison • Room 302</p>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<div style="display: grid; grid-template-columns: 240px 1fr; gap: 1.5rem;">
|
|
586
|
+
<div style="background: #fff; border: 1px solid #dadce0; border-radius: 8px; padding: 1rem; height: fit-content;">
|
|
587
|
+
<h3 style="font-size: 0.95rem; margin-bottom: 0.75rem;">Upcoming</h3>
|
|
588
|
+
<p style="font-size: 0.85rem; color: #5f6368; margin-bottom: 0.5rem;">Due Friday, 11:59 PM</p>
|
|
589
|
+
<a href="#" style="color: #1a73e8; font-size: 0.88rem; text-decoration: none;">Unit 4 Practice: Derivative Applications</a>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
593
|
+
<div style="background: #fff; border: 1px solid #dadce0; border-radius: 8px; padding: 1.25rem;">
|
|
594
|
+
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;">
|
|
595
|
+
<div style="width: 36px; height: 36px; border-radius: 50%; background: #137333; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: bold;">H</div>
|
|
596
|
+
<div>
|
|
597
|
+
<strong style="font-size: 0.9rem;">Dr. Harrison</strong>
|
|
598
|
+
<div style="font-size: 0.75rem; color: #5f6368;">Posted Yesterday at 8:45 AM</div>
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
<p style="font-size: 0.88rem; line-height: 1.5; color: #3c4043;">Please review Section 4.2 in the digital textbook before tomorrow's lecture. Completed problem sets 1-15 will be collected at the start of class.</p>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
537
608
|
<!-- Toast -->
|
|
538
609
|
<div id="toast-box" class="toast-box"></div>
|
|
539
610
|
|
package/js/app.js
CHANGED
|
@@ -146,6 +146,7 @@ class ReaperHubApp {
|
|
|
146
146
|
this.updateStats();
|
|
147
147
|
this.renderChatMessages();
|
|
148
148
|
this.renderGamesGrid();
|
|
149
|
+
this.renderRecentlyPlayed();
|
|
149
150
|
this.setupEventListeners();
|
|
150
151
|
this.setupAdminShortcuts();
|
|
151
152
|
this.startTimeoutTimerLoop();
|
|
@@ -1210,6 +1211,125 @@ class ReaperHubApp {
|
|
|
1210
1211
|
</div>
|
|
1211
1212
|
`;
|
|
1212
1213
|
}).join('');
|
|
1214
|
+
|
|
1215
|
+
this.attachCardTiltListeners();
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
attachCardTiltListeners() {
|
|
1219
|
+
const cards = document.querySelectorAll('.game-card');
|
|
1220
|
+
cards.forEach(card => {
|
|
1221
|
+
card.addEventListener('mouseenter', () => {
|
|
1222
|
+
if (window.reaperSFX) window.reaperSFX.playHover();
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
card.addEventListener('mousemove', (e) => {
|
|
1226
|
+
const rect = card.getBoundingClientRect();
|
|
1227
|
+
const x = e.clientX - rect.left;
|
|
1228
|
+
const y = e.clientY - rect.top;
|
|
1229
|
+
const centerX = rect.width / 2;
|
|
1230
|
+
const centerY = rect.height / 2;
|
|
1231
|
+
|
|
1232
|
+
const rotateX = ((y - centerY) / centerY) * -12;
|
|
1233
|
+
const rotateY = ((x - centerX) / centerX) * 12;
|
|
1234
|
+
|
|
1235
|
+
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.03, 1.03, 1.03)`;
|
|
1236
|
+
card.style.setProperty('--mouse-x', `${x}px`);
|
|
1237
|
+
card.style.setProperty('--mouse-y', `${y}px`);
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
card.addEventListener('mouseleave', () => {
|
|
1241
|
+
card.style.transform = '';
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
recordRecentlyPlayed(game) {
|
|
1247
|
+
if (!game) return;
|
|
1248
|
+
let recent = JSON.parse(localStorage.getItem('reaper_recently_played') || '[]');
|
|
1249
|
+
recent = recent.filter(r => r.id !== game.id);
|
|
1250
|
+
recent.unshift({ id: game.id, name: game.name, cover: game.cover });
|
|
1251
|
+
if (recent.length > 6) recent = recent.slice(0, 6);
|
|
1252
|
+
localStorage.setItem('reaper_recently_played', JSON.stringify(recent));
|
|
1253
|
+
this.renderRecentlyPlayed();
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
renderRecentlyPlayed() {
|
|
1257
|
+
const container = document.getElementById('recent-games-container');
|
|
1258
|
+
const grid = document.getElementById('recent-games-grid');
|
|
1259
|
+
if (!container || !grid) return;
|
|
1260
|
+
|
|
1261
|
+
const recent = JSON.parse(localStorage.getItem('reaper_recently_played') || '[]');
|
|
1262
|
+
if (recent.length === 0) {
|
|
1263
|
+
container.style.display = 'none';
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
container.style.display = 'block';
|
|
1268
|
+
grid.innerHTML = recent.map(g => {
|
|
1269
|
+
const coverUrl = g.cover || `https://placehold.co/400x250/041022/ffffff?text=${encodeURIComponent(g.name)}`;
|
|
1270
|
+
return `
|
|
1271
|
+
<div class="recent-card" onclick="app.launchGame(${g.id})">
|
|
1272
|
+
<img class="recent-card-img" src="${coverUrl}" alt="${g.name}" loading="lazy" onerror="this.onerror=null; this.src='https://placehold.co/400x250/041022/ffffff?text=${encodeURIComponent(g.name)}';" />
|
|
1273
|
+
<div class="recent-card-name" title="${g.name}">${this.escapeHtml(g.name)}</div>
|
|
1274
|
+
</div>
|
|
1275
|
+
`;
|
|
1276
|
+
}).join('');
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
clearRecentlyPlayed() {
|
|
1280
|
+
localStorage.removeItem('reaper_recently_played');
|
|
1281
|
+
this.renderRecentlyPlayed();
|
|
1282
|
+
this.showToast("Recently played list cleared.");
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
exportSaveData() {
|
|
1286
|
+
const backup = {};
|
|
1287
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
1288
|
+
const key = localStorage.key(i);
|
|
1289
|
+
backup[key] = localStorage.getItem(key);
|
|
1290
|
+
}
|
|
1291
|
+
const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
|
|
1292
|
+
const url = URL.createObjectURL(blob);
|
|
1293
|
+
const a = document.createElement('a');
|
|
1294
|
+
a.href = url;
|
|
1295
|
+
a.download = `reaper_save_backup_${new Date().toISOString().slice(0,10)}.json`;
|
|
1296
|
+
a.click();
|
|
1297
|
+
URL.revokeObjectURL(url);
|
|
1298
|
+
this.showToast("Game save data downloaded! 💾");
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
importSaveData() {
|
|
1302
|
+
const input = document.createElement('input');
|
|
1303
|
+
input.type = 'file';
|
|
1304
|
+
input.accept = '.json';
|
|
1305
|
+
input.onchange = (e) => {
|
|
1306
|
+
const file = e.target.files[0];
|
|
1307
|
+
if (!file) return;
|
|
1308
|
+
const reader = new FileReader();
|
|
1309
|
+
reader.onload = (event) => {
|
|
1310
|
+
try {
|
|
1311
|
+
const data = JSON.parse(event.target.result);
|
|
1312
|
+
Object.entries(data).forEach(([k, v]) => {
|
|
1313
|
+
localStorage.setItem(k, v);
|
|
1314
|
+
});
|
|
1315
|
+
this.showToast("Save data restored! Reloading...");
|
|
1316
|
+
setTimeout(() => window.location.reload(), 1200);
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
alert("Invalid backup file.");
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
reader.readAsText(file);
|
|
1322
|
+
};
|
|
1323
|
+
input.click();
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
toggleSoundFX() {
|
|
1327
|
+
if (window.reaperSFX) {
|
|
1328
|
+
const enabled = window.reaperSFX.toggleSound();
|
|
1329
|
+
const btn = document.getElementById('btn-sfx-toggle');
|
|
1330
|
+
if (btn) btn.textContent = enabled ? '🔊 SFX' : '🔇 MUTED';
|
|
1331
|
+
this.showToast(enabled ? "Sound FX Enabled" : "Sound FX Muted");
|
|
1332
|
+
}
|
|
1213
1333
|
}
|
|
1214
1334
|
|
|
1215
1335
|
toggleFavorite(id) {
|
|
@@ -1237,6 +1357,13 @@ class ReaperHubApp {
|
|
|
1237
1357
|
const game = this.games.find(g => g.id === gameId);
|
|
1238
1358
|
if (!game) return;
|
|
1239
1359
|
this.currentGame = game;
|
|
1360
|
+
this.recordRecentlyPlayed(game);
|
|
1361
|
+
|
|
1362
|
+
if (window.reaperSFX) window.reaperSFX.playLaunch();
|
|
1363
|
+
if (window.reaperStarfield) {
|
|
1364
|
+
window.reaperStarfield.setWarpSpeed(true);
|
|
1365
|
+
setTimeout(() => window.reaperStarfield.setWarpSpeed(false), 1200);
|
|
1366
|
+
}
|
|
1240
1367
|
|
|
1241
1368
|
const modal = document.getElementById('game-player-modal');
|
|
1242
1369
|
const iframe = document.getElementById('player-iframe');
|
package/js/security-shield.js
CHANGED
|
@@ -49,6 +49,14 @@
|
|
|
49
49
|
e.stopPropagation();
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
|
+
// Emergency Panic Cloak Hotkey (~ or Backquote)
|
|
53
|
+
if (e.key === '`' || e.key === '~') {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
this.togglePanicDisguise();
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Ctrl + U (View Source)
|
|
52
60
|
if (e.ctrlKey && (e.key === 'U' || e.key === 'u')) {
|
|
53
61
|
e.preventDefault();
|
|
54
62
|
e.stopPropagation();
|
|
@@ -215,6 +223,37 @@
|
|
|
215
223
|
return count;
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
togglePanicDisguise() {
|
|
227
|
+
const overlay = document.getElementById('panic-disguise-overlay');
|
|
228
|
+
if (!overlay) return;
|
|
229
|
+
|
|
230
|
+
this.isPanicActive = !this.isPanicActive;
|
|
231
|
+
|
|
232
|
+
if (this.isPanicActive) {
|
|
233
|
+
this.originalTitle = document.title;
|
|
234
|
+
document.title = "Home - Google Classroom";
|
|
235
|
+
|
|
236
|
+
let link = document.querySelector("link[rel*='icon']");
|
|
237
|
+
if (!link) {
|
|
238
|
+
link = document.createElement('link');
|
|
239
|
+
link.rel = 'shortcut icon';
|
|
240
|
+
document.head.appendChild(link);
|
|
241
|
+
}
|
|
242
|
+
this.originalFavicon = link.href;
|
|
243
|
+
link.href = "https://ssl.gstatic.com/classroom/favicon.png";
|
|
244
|
+
|
|
245
|
+
overlay.style.display = 'block';
|
|
246
|
+
if (window.reaperSFX) window.reaperSFX.playClick();
|
|
247
|
+
} else {
|
|
248
|
+
document.title = this.originalTitle || "REAPER // Games & Leaderboard";
|
|
249
|
+
const link = document.querySelector("link[rel*='icon']");
|
|
250
|
+
if (link && this.originalFavicon) {
|
|
251
|
+
link.href = this.originalFavicon;
|
|
252
|
+
}
|
|
253
|
+
overlay.style.display = 'none';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
218
257
|
setAutoPurge(enabled) {
|
|
219
258
|
this.autoShield = enabled;
|
|
220
259
|
localStorage.setItem('reaper_auto_shield', enabled ? 'true' : 'false');
|
package/js/visuals.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// REAPER HUB -
|
|
1
|
+
// REAPER HUB - Next-Gen Starfield & Web Audio SFX Synthesizer Engine
|
|
2
2
|
|
|
3
3
|
class StarfieldCanvas {
|
|
4
4
|
constructor() {
|
|
@@ -6,9 +6,11 @@ class StarfieldCanvas {
|
|
|
6
6
|
if (!this.canvas) return;
|
|
7
7
|
this.ctx = this.canvas.getContext('2d');
|
|
8
8
|
this.stars = [];
|
|
9
|
-
this.numStars =
|
|
9
|
+
this.numStars = 240;
|
|
10
10
|
this.width = window.innerWidth;
|
|
11
11
|
this.height = window.innerHeight;
|
|
12
|
+
this.isWarping = false;
|
|
13
|
+
this.warpSpeedMultiplier = 1;
|
|
12
14
|
|
|
13
15
|
this.init();
|
|
14
16
|
}
|
|
@@ -17,16 +19,17 @@ class StarfieldCanvas {
|
|
|
17
19
|
this.resize();
|
|
18
20
|
window.addEventListener('resize', () => this.resize());
|
|
19
21
|
|
|
20
|
-
// Generate Stars
|
|
22
|
+
// Generate Space Particle Stars
|
|
21
23
|
this.stars = [];
|
|
22
24
|
for (let i = 0; i < this.numStars; i++) {
|
|
23
25
|
this.stars.push({
|
|
24
26
|
x: Math.random() * this.width,
|
|
25
27
|
y: Math.random() * this.height,
|
|
26
|
-
radius: Math.random() * 1.
|
|
28
|
+
radius: Math.random() * 1.6 + 0.4,
|
|
27
29
|
alpha: Math.random() * 0.8 + 0.2,
|
|
28
30
|
twinkleSpeed: (Math.random() * 0.02 + 0.005) * (Math.random() > 0.5 ? 1 : -1),
|
|
29
|
-
speedY: (Math.random() * 0.
|
|
31
|
+
speedY: (Math.random() * 0.25 + 0.08),
|
|
32
|
+
color: Math.random() > 0.8 ? '#00d2ff' : Math.random() > 0.9 ? '#8800ff' : '#ffffff'
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
|
|
@@ -42,8 +45,15 @@ class StarfieldCanvas {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
setWarpSpeed(enabled) {
|
|
49
|
+
this.isWarping = enabled;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
animate() {
|
|
46
|
-
this.ctx.
|
|
53
|
+
this.ctx.fillStyle = 'rgba(2, 5, 10, 0.25)';
|
|
54
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
55
|
+
|
|
56
|
+
const speedMult = this.isWarping ? 12 : 1;
|
|
47
57
|
|
|
48
58
|
for (let star of this.stars) {
|
|
49
59
|
star.alpha += star.twinkleSpeed;
|
|
@@ -51,24 +61,171 @@ class StarfieldCanvas {
|
|
|
51
61
|
star.twinkleSpeed = -star.twinkleSpeed;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
star.y -= star.speedY;
|
|
64
|
+
star.y -= star.speedY * speedMult;
|
|
55
65
|
if (star.y < 0) {
|
|
56
66
|
star.y = this.height;
|
|
57
67
|
star.x = Math.random() * this.width;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
this.ctx.beginPath();
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
if (this.isWarping) {
|
|
72
|
+
this.ctx.moveTo(star.x, star.y);
|
|
73
|
+
this.ctx.lineTo(star.x, star.y + star.speedY * 18);
|
|
74
|
+
this.ctx.strokeStyle = star.color;
|
|
75
|
+
this.ctx.lineWidth = star.radius;
|
|
76
|
+
this.ctx.stroke();
|
|
77
|
+
} else {
|
|
78
|
+
this.ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
|
|
79
|
+
this.ctx.fillStyle = star.color;
|
|
80
|
+
this.ctx.globalAlpha = Math.max(0.1, Math.min(1, star.alpha));
|
|
81
|
+
this.ctx.shadowBlur = 4;
|
|
82
|
+
this.ctx.shadowColor = star.color;
|
|
83
|
+
this.ctx.fill();
|
|
84
|
+
this.ctx.shadowBlur = 0;
|
|
85
|
+
this.ctx.globalAlpha = 1;
|
|
86
|
+
}
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
requestAnimationFrame(() => this.animate());
|
|
69
90
|
}
|
|
70
91
|
}
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
// Web Audio Sci-Fi Synthesizer (Zero Dependencies)
|
|
94
|
+
class ReaperSoundFX {
|
|
95
|
+
constructor() {
|
|
96
|
+
this.audioCtx = null;
|
|
97
|
+
this.isEnabled = localStorage.getItem('reaper_sfx_enabled') !== 'false';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
initContext() {
|
|
101
|
+
if (!this.audioCtx) {
|
|
102
|
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
103
|
+
if (AudioContext) {
|
|
104
|
+
this.audioCtx = new AudioContext();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (this.audioCtx && this.audioCtx.state === 'suspended') {
|
|
108
|
+
this.audioCtx.resume();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
playHover() {
|
|
113
|
+
if (!this.isEnabled) return;
|
|
114
|
+
try {
|
|
115
|
+
this.initContext();
|
|
116
|
+
if (!this.audioCtx) return;
|
|
117
|
+
const osc = this.audioCtx.createOscillator();
|
|
118
|
+
const gain = this.audioCtx.createGain();
|
|
119
|
+
|
|
120
|
+
osc.type = 'sine';
|
|
121
|
+
osc.frequency.setValueAtTime(880, this.audioCtx.currentTime);
|
|
122
|
+
osc.frequency.exponentialRampToValueAtTime(1200, this.audioCtx.currentTime + 0.04);
|
|
123
|
+
|
|
124
|
+
gain.gain.setValueAtTime(0.03, this.audioCtx.currentTime);
|
|
125
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, this.audioCtx.currentTime + 0.04);
|
|
126
|
+
|
|
127
|
+
osc.connect(gain);
|
|
128
|
+
gain.connect(this.audioCtx.destination);
|
|
129
|
+
|
|
130
|
+
osc.start();
|
|
131
|
+
osc.stop(this.audioCtx.currentTime + 0.04);
|
|
132
|
+
} catch (e) {}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
playClick() {
|
|
136
|
+
if (!this.isEnabled) return;
|
|
137
|
+
try {
|
|
138
|
+
this.initContext();
|
|
139
|
+
if (!this.audioCtx) return;
|
|
140
|
+
const osc = this.audioCtx.createOscillator();
|
|
141
|
+
const gain = this.audioCtx.createGain();
|
|
142
|
+
|
|
143
|
+
osc.type = 'triangle';
|
|
144
|
+
osc.frequency.setValueAtTime(520, this.audioCtx.currentTime);
|
|
145
|
+
osc.frequency.exponentialRampToValueAtTime(180, this.audioCtx.currentTime + 0.08);
|
|
146
|
+
|
|
147
|
+
gain.gain.setValueAtTime(0.08, this.audioCtx.currentTime);
|
|
148
|
+
gain.gain.exponentialRampToValueAtTime(0.001, this.audioCtx.currentTime + 0.08);
|
|
149
|
+
|
|
150
|
+
osc.connect(gain);
|
|
151
|
+
gain.connect(this.audioCtx.destination);
|
|
152
|
+
|
|
153
|
+
osc.start();
|
|
154
|
+
osc.stop(this.audioCtx.currentTime + 0.08);
|
|
155
|
+
} catch (e) {}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
playLaunch() {
|
|
159
|
+
if (!this.isEnabled) return;
|
|
160
|
+
try {
|
|
161
|
+
this.initContext();
|
|
162
|
+
if (!this.audioCtx) return;
|
|
163
|
+
const now = this.audioCtx.currentTime;
|
|
164
|
+
|
|
165
|
+
const osc1 = this.audioCtx.createOscillator();
|
|
166
|
+
const osc2 = this.audioCtx.createOscillator();
|
|
167
|
+
const gain = this.audioCtx.createGain();
|
|
168
|
+
|
|
169
|
+
osc1.type = 'sawtooth';
|
|
170
|
+
osc2.type = 'sine';
|
|
171
|
+
|
|
172
|
+
osc1.frequency.setValueAtTime(220, now);
|
|
173
|
+
osc1.frequency.exponentialRampToValueAtTime(880, now + 0.35);
|
|
174
|
+
|
|
175
|
+
osc2.frequency.setValueAtTime(440, now);
|
|
176
|
+
osc2.frequency.exponentialRampToValueAtTime(1760, now + 0.35);
|
|
177
|
+
|
|
178
|
+
gain.gain.setValueAtTime(0.09, now);
|
|
179
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.4);
|
|
180
|
+
|
|
181
|
+
osc1.connect(gain);
|
|
182
|
+
osc2.connect(gain);
|
|
183
|
+
gain.connect(this.audioCtx.destination);
|
|
184
|
+
|
|
185
|
+
osc1.start(now);
|
|
186
|
+
osc2.start(now);
|
|
187
|
+
osc1.stop(now + 0.4);
|
|
188
|
+
osc2.stop(now + 0.4);
|
|
189
|
+
} catch (e) {}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
playUnlock() {
|
|
193
|
+
if (!this.isEnabled) return;
|
|
194
|
+
try {
|
|
195
|
+
this.initContext();
|
|
196
|
+
if (!this.audioCtx) return;
|
|
197
|
+
const now = this.audioCtx.currentTime;
|
|
198
|
+
const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
|
|
199
|
+
|
|
200
|
+
notes.forEach((freq, idx) => {
|
|
201
|
+
const osc = this.audioCtx.createOscillator();
|
|
202
|
+
const gain = this.audioCtx.createGain();
|
|
203
|
+
const start = now + (idx * 0.07);
|
|
204
|
+
|
|
205
|
+
osc.type = 'sine';
|
|
206
|
+
osc.frequency.setValueAtTime(freq, start);
|
|
207
|
+
|
|
208
|
+
gain.gain.setValueAtTime(0.08, start);
|
|
209
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, start + 0.22);
|
|
210
|
+
|
|
211
|
+
osc.connect(gain);
|
|
212
|
+
gain.connect(this.audioCtx.destination);
|
|
213
|
+
|
|
214
|
+
osc.start(start);
|
|
215
|
+
osc.stop(start + 0.22);
|
|
216
|
+
});
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
toggleSound() {
|
|
221
|
+
this.isEnabled = !this.isEnabled;
|
|
222
|
+
localStorage.setItem('reaper_sfx_enabled', this.isEnabled ? 'true' : 'false');
|
|
223
|
+
if (this.isEnabled) this.playClick();
|
|
224
|
+
return this.isEnabled;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
229
|
+
window.reaperStarfield = new StarfieldCanvas();
|
|
230
|
+
window.reaperSFX = new ReaperSoundFX();
|
|
74
231
|
});
|