reaper-arcade-hub 2.9.0 → 2.9.2

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/index.html CHANGED
@@ -38,7 +38,7 @@
38
38
  <div class="loader-brand-title">reaper</div>
39
39
  <div class="loader-badge-tag">
40
40
  <span class="badge-dot-live"></span>
41
- <span>V2.8.0 HYPER-ENGINE</span> • 788 GAMES • ZERO LAG
41
+ <span>V2.9.2 ZERO-LAG ENGINE</span> • 788 GAMES • LOCAL SAVE ENABLED
42
42
  </div>
43
43
 
44
44
  <!-- Live Terminal Kernel Diagnostics -->
package/js/app.js CHANGED
@@ -1,6 +1,6 @@
1
- // REAPER HUB - Core Engine (v2.9.0 100% AI Recode, Cyber Home Dashboard & Chat Overhaul)
2
- window.REAPER_VERSION = "2.9.0";
3
- window.REAPER_LATEST_URL = "https://unpkg.com/reaper-arcade-hub@2.9.0/main/index.html";
1
+ // REAPER HUB - Core Engine (v2.9.2 Persistent Local Save Engine & 60 FPS about:blank Isolation)
2
+ window.REAPER_VERSION = "2.9.2";
3
+ window.REAPER_LATEST_URL = "https://unpkg.com/reaper-arcade-hub@2.9.2/main/index.html";
4
4
 
5
5
  function _isMasterOwnerToken(k) {
6
6
  if (!k) return false;
@@ -21,8 +21,8 @@ class ReaperHubApp {
21
21
  };
22
22
  this.isAdmin = this.currentUser.rank === 'owner';
23
23
  this.bannedUsers = JSON.parse(localStorage.getItem('reaper_banned_users') || '[]');
24
- this.siteLockdown = localStorage.getItem('reaper_site_lockdown') === 'true';
25
- this.isChatLocked = localStorage.getItem('reaper_chat_locked') === 'true';
24
+ this.siteLockdown = false; // Always start clean so visitors are never trapped by stale localStorage
25
+ this.isChatLocked = false;
26
26
  this.aiBypass = localStorage.getItem('reaper_ai_bypass') === 'true';
27
27
  this.activeTab = 'home';
28
28
  this.activeCategory = 'all';
@@ -94,11 +94,11 @@ class ReaperHubApp {
94
94
  this.isAdmin = true;
95
95
  }
96
96
 
97
- // 7. Site lockdown
98
- if (this.siteLockdown && this.currentUser.rank !== 'owner') {
99
- this.renderLockdownScreen();
100
- return;
101
- }
97
+ // 7. Cleanse any stale lockdown flags from old visits
98
+ try {
99
+ localStorage.removeItem('reaper_site_lockdown');
100
+ localStorage.removeItem('reaper_verify_lockdown');
101
+ } catch (e) {}
102
102
 
103
103
  // 8. Connect Firebase
104
104
  if (window.reaperFirebase) {
@@ -283,7 +283,19 @@ class ReaperHubApp {
283
283
  }
284
284
  }
285
285
 
286
- // 11. Auto-show Update Logs on visit if new version
286
+ // 11. Realtime Persistent Game Save Synchronizer for about:blank tabs
287
+ window.addEventListener('message', (e) => {
288
+ if (e.data && e.data.type === 'REAPER_SAVE_SYNC' && e.data.gameId) {
289
+ try {
290
+ const key = 'reaper_save_' + e.data.gameId;
291
+ const current = JSON.parse(localStorage.getItem(key) || '{}');
292
+ Object.assign(current, e.data.data);
293
+ localStorage.setItem(key, JSON.stringify(current));
294
+ } catch(err) {}
295
+ }
296
+ });
297
+
298
+ // 12. Auto-show Update Logs on visit if new version
287
299
  const lastSeenVersion = localStorage.getItem('reaper_last_seen_version');
288
300
  if (lastSeenVersion !== '1.0.9') {
289
301
  setTimeout(() => {
@@ -2515,31 +2527,8 @@ Query: **"${prompt}"**
2515
2527
  }
2516
2528
 
2517
2529
  attachCardTiltListeners() {
2518
- const cards = document.querySelectorAll('.game-card');
2519
- cards.forEach(card => {
2520
- card.addEventListener('mouseenter', () => {
2521
- if (window.reaperSFX) window.reaperSFX.playHover();
2522
- });
2523
-
2524
- card.addEventListener('mousemove', (e) => {
2525
- const rect = card.getBoundingClientRect();
2526
- const x = e.clientX - rect.left;
2527
- const y = e.clientY - rect.top;
2528
- const centerX = rect.width / 2;
2529
- const centerY = rect.height / 2;
2530
-
2531
- const rotateX = ((y - centerY) / centerY) * -12;
2532
- const rotateY = ((x - centerX) / centerX) * 12;
2533
-
2534
- card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.03, 1.03, 1.03)`;
2535
- card.style.setProperty('--mouse-x', `${x}px`);
2536
- card.style.setProperty('--mouse-y', `${y}px`);
2537
- });
2538
-
2539
- card.addEventListener('mouseleave', () => {
2540
- card.style.transform = '';
2541
- });
2542
- });
2530
+ // Zero-lag optimization: Heavy per-pixel getBoundingClientRect() tilt calculations
2531
+ // across 788 cards are disabled in favor of hardware-accelerated CSS 3D transforms.
2543
2532
  }
2544
2533
 
2545
2534
  recordRecentlyPlayed(game) {
@@ -2611,7 +2600,7 @@ Query: **"${prompt}"**
2611
2600
  }
2612
2601
  }
2613
2602
 
2614
- cleanAndOptimizeGameHTML(html) {
2603
+ cleanAndOptimizeGameHTML(html, gameId) {
2615
2604
  if (!html || typeof html !== 'string') return '';
2616
2605
  let cleaned = html;
2617
2606
 
@@ -2620,8 +2609,10 @@ Query: **"${prompt}"**
2620
2609
  cleaned = cleaned.replace(/<div id="sidebarad2"[\s\S]*?<\/div>\s*<\/div>/gi, '');
2621
2610
  cleaned = cleaned.replace(/<style>[\s\S]*?#sidebarad1[\s\S]*?<\/style>/gi, '');
2622
2611
 
2623
- // 2. Inject high-performance responsive scaling, centering & click-focus engine
2624
- const injectStyles = `
2612
+ const safeGameId = String(gameId || (this.currentGame ? this.currentGame.id : 'default'));
2613
+
2614
+ // 2. Inject high-performance responsive scaling & Universal Persistent Local Save Engine
2615
+ const injectStylesAndSave = `
2625
2616
  <style id="reaper-game-enhancer">
2626
2617
  * { box-sizing: border-box; }
2627
2618
  html, body {
@@ -2655,22 +2646,158 @@ Query: **"${prompt}"**
2655
2646
  z-index: -9999 !important;
2656
2647
  }
2657
2648
  </style>
2658
- <script>
2659
- // Neutralize ad popups & focus game canvas
2660
- window.open = function() { return null; };
2661
- window.addEventListener('load', function() {
2662
- const c = document.querySelector('canvas') || document.querySelector('#game');
2663
- if (c && c.focus) c.focus();
2664
- });
2649
+ <script id="reaper-save-engine">
2650
+ (function() {
2651
+ var GAME_ID = "` + safeGameId + `";
2652
+ var STORAGE_KEY = "reaper_save_" + GAME_ID;
2653
+ var memoryStore = {};
2654
+
2655
+ // Initial State Restoration from Parent Window or Opener
2656
+ try {
2657
+ if (window.parent && window.parent.REAPER_INITIAL_SAVE) {
2658
+ var p = window.parent.REAPER_INITIAL_SAVE;
2659
+ var parsed = typeof p === 'string' ? JSON.parse(p) : p;
2660
+ if (parsed && typeof parsed === 'object') Object.assign(memoryStore, parsed);
2661
+ }
2662
+ } catch(e) {}
2663
+
2664
+ if (Object.keys(memoryStore).length === 0) {
2665
+ try {
2666
+ if (window.parent && window.parent.localStorage) {
2667
+ var raw = window.parent.localStorage.getItem(STORAGE_KEY);
2668
+ if (raw) Object.assign(memoryStore, JSON.parse(raw));
2669
+ }
2670
+ } catch(e) {}
2671
+ }
2672
+
2673
+ if (Object.keys(memoryStore).length === 0) {
2674
+ try {
2675
+ if (window.opener && window.opener.localStorage) {
2676
+ var rawOpener = window.opener.localStorage.getItem(STORAGE_KEY);
2677
+ if (rawOpener) Object.assign(memoryStore, JSON.parse(rawOpener));
2678
+ }
2679
+ } catch(e) {}
2680
+ }
2681
+
2682
+ function persistSave() {
2683
+ var dataStr = JSON.stringify(memoryStore);
2684
+ try {
2685
+ if (window.parent && window.parent.localStorage) {
2686
+ window.parent.localStorage.setItem(STORAGE_KEY, dataStr);
2687
+ }
2688
+ } catch(e) {}
2689
+ try {
2690
+ if (window.opener && window.opener.localStorage) {
2691
+ window.opener.localStorage.setItem(STORAGE_KEY, dataStr);
2692
+ }
2693
+ } catch(e) {}
2694
+ try {
2695
+ var payload = { type: 'REAPER_SAVE_SYNC', gameId: GAME_ID, data: memoryStore };
2696
+ if (window.parent && window.parent !== window) {
2697
+ window.parent.postMessage(payload, '*');
2698
+ }
2699
+ if (window.opener) {
2700
+ window.opener.postMessage(payload, '*');
2701
+ }
2702
+ } catch(e) {}
2703
+ }
2704
+
2705
+ // Virtual Storage Implementation
2706
+ var virtualStorage = {
2707
+ getItem: function(k) {
2708
+ var key = String(k);
2709
+ return memoryStore.hasOwnProperty(key) ? memoryStore[key] : null;
2710
+ },
2711
+ setItem: function(k, v) {
2712
+ var key = String(k);
2713
+ var val = String(v);
2714
+ memoryStore[key] = val;
2715
+ persistSave();
2716
+ },
2717
+ removeItem: function(k) {
2718
+ var key = String(k);
2719
+ delete memoryStore[key];
2720
+ persistSave();
2721
+ },
2722
+ clear: function() {
2723
+ memoryStore = {};
2724
+ persistSave();
2725
+ },
2726
+ key: function(i) {
2727
+ var keys = Object.keys(memoryStore);
2728
+ return keys[i] !== undefined ? keys[i] : null;
2729
+ },
2730
+ get length() {
2731
+ return Object.keys(memoryStore).length;
2732
+ }
2733
+ };
2734
+
2735
+ // Proxy Handler for property access like storage.score = 50 or storage['save']
2736
+ var storageProxy = new Proxy(virtualStorage, {
2737
+ get: function(target, prop) {
2738
+ if (prop in target || typeof prop === 'symbol') {
2739
+ return target[prop];
2740
+ }
2741
+ return target.getItem(prop);
2742
+ },
2743
+ set: function(target, prop, val) {
2744
+ if (prop in target) {
2745
+ target[prop] = val;
2746
+ return true;
2747
+ }
2748
+ target.setItem(prop, val);
2749
+ return true;
2750
+ },
2751
+ deleteProperty: function(target, prop) {
2752
+ target.removeItem(prop);
2753
+ return true;
2754
+ }
2755
+ });
2756
+
2757
+ // Override localStorage & sessionStorage
2758
+ try {
2759
+ Object.defineProperty(window, 'localStorage', {
2760
+ value: storageProxy,
2761
+ configurable: true,
2762
+ enumerable: true,
2763
+ writable: true
2764
+ });
2765
+ } catch(e) {
2766
+ try { window.localStorage = storageProxy; } catch(e2) {}
2767
+ }
2768
+
2769
+ try {
2770
+ Object.defineProperty(window, 'sessionStorage', {
2771
+ value: storageProxy,
2772
+ configurable: true,
2773
+ enumerable: true,
2774
+ writable: true
2775
+ });
2776
+ } catch(e) {
2777
+ try { window.sessionStorage = storageProxy; } catch(e2) {}
2778
+ }
2779
+
2780
+ // Periodic & unload autosave sync
2781
+ setInterval(persistSave, 1500);
2782
+ window.addEventListener('beforeunload', persistSave);
2783
+ window.addEventListener('pagehide', persistSave);
2784
+
2785
+ // Focus game canvas on load & block intrusive popups
2786
+ window.open = function() { return null; };
2787
+ window.addEventListener('load', function() {
2788
+ var c = document.querySelector('canvas') || document.querySelector('#game');
2789
+ if (c && c.focus) c.focus();
2790
+ });
2791
+ })();
2665
2792
  <\/script>
2666
2793
  `;
2667
2794
 
2668
2795
  if (cleaned.includes('<head>')) {
2669
- cleaned = cleaned.replace('<head>', '<head>' + injectStyles);
2796
+ cleaned = cleaned.replace('<head>', '<head>' + injectStylesAndSave);
2670
2797
  } else if (cleaned.includes('<html>')) {
2671
- cleaned = cleaned.replace('<html>', '<html><head>' + injectStyles + '</head>');
2798
+ cleaned = cleaned.replace('<html>', '<html><head>' + injectStylesAndSave + '</head>');
2672
2799
  } else {
2673
- cleaned = injectStyles + cleaned;
2800
+ cleaned = injectStylesAndSave + cleaned;
2674
2801
  }
2675
2802
 
2676
2803
  return cleaned;
@@ -2701,6 +2828,11 @@ Query: **"${prompt}"**
2701
2828
  window.reaperStarfield.pause();
2702
2829
  }
2703
2830
 
2831
+ // Auto open in clean about:blank tab for 60 FPS zero-lag gameplay & school cloaking!
2832
+ this.openGameAboutBlank(game);
2833
+ }
2834
+
2835
+ openGameInModal(game) {
2704
2836
  const modal = document.getElementById('game-player-modal');
2705
2837
  const iframe = document.getElementById('player-iframe');
2706
2838
  const nameEl = document.getElementById('player-game-name');
@@ -2721,37 +2853,31 @@ Query: **"${prompt}"**
2721
2853
  const filename = game.url.split('/').pop();
2722
2854
  iframe.removeAttribute('srcdoc');
2723
2855
 
2724
- // 1. Instant Memory Cache Check (0.01s Load)
2725
2856
  if (this.turboGameCache && this.turboGameCache[filename]) {
2726
- iframe.srcdoc = this.cleanAndOptimizeGameHTML(this.turboGameCache[filename]);
2857
+ iframe.srcdoc = this.cleanAndOptimizeGameHTML(this.turboGameCache[filename], game.id);
2727
2858
  if (statusBadge) statusBadge.textContent = '⚡ TURBO INSTANT (60 FPS)';
2728
2859
  return;
2729
2860
  }
2730
2861
 
2731
- // 2. High-Speed Direct Official CDN (GitHub Pages gn-math.github.io)
2732
- try {
2733
- const res = await fetch(`https://gn-math.github.io/html/${filename}`);
2734
- if (res.ok) {
2735
- let html = await res.text();
2862
+ fetch(`https://gn-math.github.io/html/${filename}`)
2863
+ .then(res => {
2864
+ if (res.ok) return res.text();
2865
+ throw new Error("CDN fallback");
2866
+ })
2867
+ .then(html => {
2736
2868
  if (html && html.length > 50) {
2737
2869
  this.turboGameCache[filename] = html;
2738
- iframe.srcdoc = this.cleanAndOptimizeGameHTML(html);
2870
+ iframe.srcdoc = this.cleanAndOptimizeGameHTML(html, game.id);
2739
2871
  if (statusBadge) statusBadge.textContent = 'ONLINE (60 FPS)';
2740
- return;
2872
+ } else {
2873
+ iframe.src = `https://gn-math.github.io/html/${filename}`;
2874
+ if (statusBadge) statusBadge.textContent = 'ONLINE';
2741
2875
  }
2742
- }
2743
- } catch (err) {
2744
- console.warn("Direct fetch fallback:", err);
2745
- }
2746
-
2747
- // 3. Fallback to direct iframe URL
2748
- try {
2749
- iframe.src = `https://gn-math.github.io/html/${filename}`;
2750
- if (statusBadge) statusBadge.textContent = 'ONLINE';
2751
- } catch(e) {
2752
- iframe.src = `https://fastly.jsdelivr.net/gh/gn-math/html@main/${filename}`;
2753
- if (statusBadge) statusBadge.textContent = 'ONLINE';
2754
- }
2876
+ })
2877
+ .catch(() => {
2878
+ iframe.src = `https://fastly.jsdelivr.net/gh/gn-math/html@main/${filename}`;
2879
+ if (statusBadge) statusBadge.textContent = 'ONLINE';
2880
+ });
2755
2881
  }
2756
2882
  }
2757
2883
 
@@ -2785,92 +2911,193 @@ Query: **"${prompt}"**
2785
2911
  togglePlayerFullscreen() {
2786
2912
  const frameWrapper = document.querySelector('.player-frame-wrapper');
2787
2913
  if (!document.fullscreenElement) {
2788
- frameWrapper.requestFullscreen().catch(e => console.error(e));
2914
+ if (frameWrapper && frameWrapper.requestFullscreen) frameWrapper.requestFullscreen().catch(e => console.error(e));
2789
2915
  } else {
2790
2916
  document.exitFullscreen();
2791
2917
  }
2792
2918
  }
2793
2919
 
2794
- async openGameAboutBlank() {
2795
- if (!this.currentGame) return;
2796
- const filename = this.currentGame.url.split('/').pop();
2797
- const gameName = this.currentGame.name;
2920
+ async openGameAboutBlank(gameArg) {
2921
+ const game = gameArg || this.currentGame;
2922
+ if (!game) return;
2923
+ this.currentGame = game;
2924
+
2925
+ const filename = game.url.split('/').pop();
2926
+ const gameName = game.name;
2798
2927
  const siteUrl = window.location.href;
2799
- const win = window.open('about:blank', '_blank');
2800
- if (win) {
2928
+ const directUrl = `https://gn-math.github.io/html/${filename}`;
2929
+ const fallbackUrl = `https://fastly.jsdelivr.net/gh/gn-math/html@main/${filename}`;
2930
+
2931
+ // Read stored game save from Reaper Hub local storage
2932
+ const initialSave = localStorage.getItem('reaper_save_' + game.id) || '{}';
2933
+
2934
+ // 1. Immediately open window in user click call stack to bypass browser popup blockers
2935
+ let win = null;
2936
+ try {
2937
+ win = window.open('about:blank', '_blank');
2938
+ } catch (e) {
2939
+ console.warn("Popup blocked:", e);
2940
+ }
2941
+
2942
+ if (!win) {
2943
+ // Browser popup blocker prevented window.open -> gracefully fallback to player modal
2944
+ this.showToast("Popup blocked by browser. Opening in player modal...");
2945
+ this.openGameInModal(game);
2946
+ return;
2947
+ }
2948
+
2949
+ this.isPlayingGame = true;
2950
+ this.lastActivityTime = Date.now();
2951
+ this.isPausedForInactivity = false;
2952
+ this.updatePlaytimeStatusUI();
2953
+
2954
+ // 2. Format about:blank with Google Classroom cloak, floating control bar, and full-screen isolation
2955
+ try {
2801
2956
  const doc = win.document;
2802
2957
  doc.title = "Home - Google Classroom";
2803
-
2804
- let gameHtml = '';
2805
- if (this.turboGameCache && this.turboGameCache[filename]) {
2806
- gameHtml = this.turboGameCache[filename];
2807
- } else {
2808
- try {
2809
- const res = await fetch(`https://gn-math.github.io/html/${filename}`);
2810
- if (res.ok) {
2811
- gameHtml = await res.text();
2812
- }
2813
- } catch (e) {}
2814
- }
2815
2958
 
2816
2959
  doc.open();
2817
2960
  doc.write(`
2818
2961
  <!DOCTYPE html>
2819
- <html>
2962
+ <html lang="en">
2820
2963
  <head>
2964
+ <meta charset="UTF-8">
2965
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2821
2966
  <title>Home - Google Classroom</title>
2822
- <link rel="icon" href="https://ssl.gstatic.com/classroom/favicon.png">
2967
+ <link rel="icon" type="image/png" href="https://ssl.gstatic.com/classroom/favicon.png">
2823
2968
  <style>
2824
2969
  * { margin: 0; padding: 0; box-sizing: border-box; }
2825
- body { background: #000; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
2826
- #game-frame { width: 100vw; height: 100vh; border: none; position: fixed; top: 0; left: 0; z-index: 1; }
2970
+ html, body {
2971
+ width: 100vw; height: 100vh;
2972
+ overflow: hidden; background: #000;
2973
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2974
+ }
2975
+ #game-frame {
2976
+ width: 100vw; height: 100vh;
2977
+ border: none; position: fixed; top: 0; left: 0; z-index: 1;
2978
+ display: block; background: #000;
2979
+ }
2827
2980
  .reaper-bar {
2828
- position: fixed; top: 12px; left: 14px; z-index: 9999;
2981
+ position: fixed; top: 10px; left: 12px; z-index: 99999;
2829
2982
  display: flex; gap: 8px; align-items: center;
2830
- background: rgba(3, 8, 18, 0.88); backdrop-filter: blur(8px);
2831
- border: 1.5px solid rgba(0, 136, 255, 0.4); border-radius: 8px; padding: 6px 12px;
2832
- box-shadow: 0 4px 20px rgba(0,0,0,0.7);
2833
- opacity: 0.85; transition: opacity 0.2s ease, border-color 0.2s ease;
2983
+ background: rgba(3, 10, 24, 0.92); backdrop-filter: blur(10px);
2984
+ border: 1px solid rgba(0, 210, 255, 0.5); border-radius: 8px; padding: 6px 12px;
2985
+ box-shadow: 0 4px 25px rgba(0, 0, 0, 0.8), 0 0 15px rgba(0, 210, 255, 0.2);
2986
+ opacity: 0.9; transition: opacity 0.2s ease, border-color 0.2s ease;
2834
2987
  }
2835
2988
  .reaper-bar:hover { opacity: 1; border-color: #00d2ff; }
2989
+ .game-label {
2990
+ color: #fff; font-size: 11px; font-weight: 800; letter-spacing: 0.05em;
2991
+ margin-right: 6px; font-family: monospace;
2992
+ }
2836
2993
  .btn-action {
2837
- background: #041022; color: #00d2ff; border: 1px solid rgba(0, 136, 255, 0.4);
2838
- padding: 5px 12px; border-radius: 6px; font-size: 11px; font-weight: 800;
2994
+ background: #06162d; color: #00d2ff; border: 1px solid rgba(0, 210, 255, 0.4);
2995
+ padding: 5px 11px; border-radius: 6px; font-size: 11px; font-weight: 800;
2839
2996
  cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 4px;
2840
2997
  transition: all 0.2s ease; font-family: inherit;
2841
2998
  }
2842
- .btn-action:hover { background: #0088ff; color: #fff; box-shadow: 0 0 10px rgba(0,136,255,0.6); }
2999
+ .btn-action:hover {
3000
+ background: #0088ff; color: #fff; box-shadow: 0 0 12px rgba(0, 136, 255, 0.7);
3001
+ }
2843
3002
  .reaper-watermark {
2844
- position: fixed; bottom: 10px; right: 14px; z-index: 9999;
2845
- font-family: serif; font-weight: 900; font-size: 13px; letter-spacing: 0.12em;
2846
- color: rgba(255, 255, 255, 0.35); text-shadow: 0 0 10px rgba(0, 136, 255, 0.6);
3003
+ position: fixed; bottom: 8px; right: 12px; z-index: 99999;
3004
+ font-family: serif; font-weight: 900; font-size: 12px; letter-spacing: 0.15em;
3005
+ color: rgba(255, 255, 255, 0.35); text-shadow: 0 0 8px rgba(0, 210, 255, 0.5);
2847
3006
  pointer-events: none; user-select: none;
2848
3007
  }
3008
+ #loader-spinner {
3009
+ position: fixed; inset: 0; background: #020610; z-index: 10;
3010
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
3011
+ color: #00d2ff; font-family: monospace; font-size: 14px; gap: 14px;
3012
+ }
3013
+ .spin {
3014
+ width: 42px; height: 42px; border: 3px solid rgba(0, 210, 255, 0.2);
3015
+ border-top: 3px solid #00d2ff; border-radius: 50%;
3016
+ animation: sp 0.8s linear infinite;
3017
+ }
3018
+ @keyframes sp { to { transform: rotate(360deg); } }
2849
3019
  </style>
2850
- <script>
2851
- // Client-Side Ad & Pop-up Blocker
2852
- window.open = function() { console.log('[REAPER SHIELD] Ad pop-up blocked'); return null; };
3020
+ <script id="reaper-parent-bridge">
3021
+ // Local Save Data Hub Bridge & Relay
3022
+ window.REAPER_GAME_ID = "` + game.id + `";
3023
+ window.REAPER_INITIAL_SAVE = ` + JSON.stringify(initialSave) + `;
3024
+ window.addEventListener('message', function(e) {
3025
+ if (e.data && e.data.type === 'REAPER_SAVE_SYNC') {
3026
+ try {
3027
+ localStorage.setItem('reaper_save_' + e.data.gameId, JSON.stringify(e.data.data));
3028
+ } catch(err) {}
3029
+ try {
3030
+ if (window.opener) window.opener.postMessage(e.data, '*');
3031
+ } catch(err) {}
3032
+ }
3033
+ });
3034
+ window.open = function() { return null; };
2853
3035
  <\/script>
2854
3036
  </head>
2855
3037
  <body>
3038
+ <div id="loader-spinner">
3039
+ <div class="spin"></div>
3040
+ <div style="font-weight: 800; letter-spacing: 0.1em;">LAUNCHING ${this.escapeHtml(gameName)} (60 FPS TURBO)...</div>
3041
+ </div>
2856
3042
  <div class="reaper-bar">
2857
- <a href="${siteUrl}" class="btn-action" title="Return to Reaper Hub Portal">⚡ BACK TO SITE</a>
3043
+ <span class="game-label">⚡ ${this.escapeHtml(gameName)}</span>
3044
+ <a href="${siteUrl}" class="btn-action" title="Back to Reaper Hub">🏠 HUB</a>
2858
3045
  <button onclick="document.getElementById('game-frame').requestFullscreen()" class="btn-action">⛶ FULLSCREEN</button>
2859
3046
  <button onclick="document.getElementById('game-frame').contentWindow.location.reload()" class="btn-action">🔄 RELOAD</button>
2860
3047
  </div>
2861
- <div class="reaper-watermark">REAPER HUB</div>
2862
- <iframe id="game-frame" allow="autoplay; fullscreen; gamepad; focus-without-user-activation; pointer-lock" ${gameHtml ? '' : `src="https://raw.githubusercontent.com/gn-math/html/main/${filename}"`}></iframe>
3048
+ <div class="reaper-watermark">REAPER HUB // 60 FPS ZERO LAG</div>
3049
+ <iframe id="game-frame" allow="autoplay; fullscreen; gamepad; focus-without-user-activation; pointer-lock" style="opacity: 0; transition: opacity 0.3s ease;"></iframe>
2863
3050
  </body>
2864
3051
  </html>
2865
3052
  `);
2866
3053
  doc.close();
2867
3054
 
2868
- if (gameHtml) {
2869
- const f = doc.getElementById('game-frame');
2870
- if (f) f.srcdoc = this.cleanAndOptimizeGameHTML(gameHtml);
3055
+ const frame = doc.getElementById('game-frame');
3056
+ const spinner = doc.getElementById('loader-spinner');
3057
+
3058
+ const finalizeLoad = () => {
3059
+ if (frame) frame.style.opacity = '1';
3060
+ if (spinner) spinner.style.display = 'none';
3061
+ try {
3062
+ if (frame && frame.contentWindow) {
3063
+ frame.contentWindow.focus();
3064
+ }
3065
+ } catch(e) {}
3066
+ };
3067
+
3068
+ // 3. High-Speed Memory Pre-Cache Check
3069
+ if (this.turboGameCache && this.turboGameCache[filename]) {
3070
+ frame.srcdoc = this.cleanAndOptimizeGameHTML(this.turboGameCache[filename], game.id);
3071
+ frame.onload = finalizeLoad;
3072
+ setTimeout(finalizeLoad, 400);
3073
+ } else {
3074
+ // Direct fetch & clean injection for zero lag
3075
+ fetch(directUrl)
3076
+ .then(res => {
3077
+ if (res.ok) return res.text();
3078
+ throw new Error("Direct fetch fallback");
3079
+ })
3080
+ .then(html => {
3081
+ if (html && html.length > 50) {
3082
+ if (this.turboGameCache) this.turboGameCache[filename] = html;
3083
+ frame.srcdoc = this.cleanAndOptimizeGameHTML(html, game.id);
3084
+ frame.onload = finalizeLoad;
3085
+ setTimeout(finalizeLoad, 500);
3086
+ } else {
3087
+ frame.src = directUrl;
3088
+ frame.onload = finalizeLoad;
3089
+ }
3090
+ })
3091
+ .catch(() => {
3092
+ frame.src = fallbackUrl;
3093
+ frame.onload = finalizeLoad;
3094
+ });
2871
3095
  }
2872
3096
 
2873
- this.showToast("Game opened in about:blank tab with watermark & return controls.");
3097
+ this.showToast(`Launched "${gameName}" in about:blank (60 FPS Turbo)`);
3098
+ } catch (err) {
3099
+ console.error("about:blank initialization error:", err);
3100
+ this.openGameInModal(game);
2874
3101
  }
2875
3102
  }
2876
3103
 
@@ -2906,6 +3133,24 @@ Query: **"${prompt}"**
2906
3133
  }
2907
3134
  }
2908
3135
 
3136
+ startFPSMonitor() {
3137
+ let lastTime = performance.now();
3138
+ let frames = 0;
3139
+ const updateFps = () => {
3140
+ frames++;
3141
+ const now = performance.now();
3142
+ if (now - lastTime >= 1000) {
3143
+ this.clientFps = Math.round((frames * 1000) / (now - lastTime));
3144
+ const fpsEl = document.getElementById('stat-fps-counter');
3145
+ if (fpsEl) fpsEl.textContent = `${this.clientFps} FPS`;
3146
+ frames = 0;
3147
+ lastTime = now;
3148
+ }
3149
+ requestAnimationFrame(updateFps);
3150
+ };
3151
+ requestAnimationFrame(updateFps);
3152
+ }
3153
+
2909
3154
  handleOpiumSearch() {
2910
3155
  const input = document.getElementById('opium-search-input');
2911
3156
  if (!input || !input.value.trim()) return;
@@ -305,10 +305,10 @@ class FirebaseService {
305
305
  callback(isLocked);
306
306
  });
307
307
  } catch (e) {
308
- callback(localStorage.getItem('reaper_site_lockdown') === 'true');
308
+ callback(false);
309
309
  }
310
310
  } else {
311
- callback(localStorage.getItem('reaper_site_lockdown') === 'true');
311
+ callback(false);
312
312
  }
313
313
  }
314
314
 
@@ -331,10 +331,10 @@ class FirebaseService {
331
331
  callback(isLocked);
332
332
  });
333
333
  } catch (e) {
334
- callback(localStorage.getItem('reaper_verify_lockdown') === 'true');
334
+ callback(false);
335
335
  }
336
336
  } else {
337
- callback(localStorage.getItem('reaper_verify_lockdown') === 'true');
337
+ callback(false);
338
338
  }
339
339
  }
340
340
 
@@ -117,9 +117,9 @@
117
117
  observer.observe(target, { childList: true, subtree: true });
118
118
  }
119
119
 
120
- // 4. Auto-Purge Loop if enabled
120
+ // 4. Lightweight Idle Purge Check (MutationObserver already handles live DOM with zero lag)
121
121
  if (this.autoShield) {
122
- setInterval(() => this.purgeSpyInjections(), 800);
122
+ setInterval(() => this.purgeSpyInjections(), 30000);
123
123
  }
124
124
 
125
125
  // Initial scan
package/js/visuals.js CHANGED
@@ -12,7 +12,7 @@ class StarfieldCanvas {
12
12
  this.isWarping = false;
13
13
  this.warpSpeedMultiplier = 1;
14
14
  this.isPaused = false;
15
- this.turboMode = localStorage.getItem('reaper_turbo_mode') === 'true';
15
+ this.turboMode = localStorage.getItem('reaper_turbo_mode') !== 'false'; // Default TRUE for ultra 60 FPS
16
16
  this.rafId = null;
17
17
 
18
18
  this.init();