1/** 2 * å ¨å±è¯ç¨æ¬¡æ°ç®¡çå¨ - è·¨è¯è¨çæ¬åæ¥ 3 * Global Trial Manager - Cross-language synchronization 4 */ 5 6class GlobalTrialManager { 7 constructor() { 8 // å ¨å±é®åï¼ææè¯è¨çæ¬å ±äº« 9 this.GLOBAL_TRIAL_KEY = 'grok4_global_trial_used'; 10 this.GLOBAL_TRIAL_COUNT_KEY = 'grok4_global_trial_count'; 11 this.GLOBAL_TRIAL_TIMESTAMP_KEY = 'grok4_global_trial_timestamp'; 12 13 // æµè§å¨æçº¹çæ 14 this.fingerprint = this.generateBrowserFingerprint(); 15 16 this.init(); 17 } 18 19 init() { 20 // åå§åè¯ç¨ç¶æ 21 if (!localStorage.getItem(this.GLOBAL_TRIAL_KEY)) { 22 this.resetTrialStatus(); 23 } 24 } 25 26 generateBrowserFingerprint() { 27 // çæåºäºæµè§å¨ç¯å¢çæçº¹ 28 const canvas = document.createElement('canvas'); 29 const ctx = canvas.getContext('2d'); 30 ctx.textBaseline = 'top'; 31 ctx.font = '14px Arial'; 32 ctx.fillText('Browser fingerprint', 2, 2); 33 34 const fingerprint = [ 35 navigator.userAgent, 36 navigator.language, 37 screen.width + 'x' + screen.height, 38 new Date().getTimezoneOffset(), 39 canvas.toDataURL() 40 ].join('|'); 41 42 // ç®ååå¸ 43 let hash = 0; 44 for (let i = 0; i < fingerprint.length; i++) { 45 const char = fingerprint.charCodeAt(i); 46 hash = ((hash << 5) - hash) + char; 47 hash = hash & hash; // 转æ¢ä¸º32使´æ° 48 } 49 50 return Math.abs(hash).toString(36); 51 } 52 53 hasTriedBefore() { 54 const globalUsed = localStorage.getItem(this.GLOBAL_TRIAL_KEY) === 'true'; 55 const fingerprintUsed = localStorage.getItem(`trial_fp_${this.fingerprint}`) === 'true'; 56 57 return globalUsed || fingerprintUsed; 58 } 59 60 getRemainingTrials() { 61 return this.hasTriedBefore() ? 0 : 1; 62 } 63 64 consumeTrial() { 65 if (this.hasTriedBefore()) { 66 return false; // å·²ç»ä½¿ç¨è¿äº 67 } 68 69 // æ è®°ä¸ºå·²ä½¿ç¨ 70 localStorage.setItem(this.GLOBAL_TRIAL_KEY, 'true'); 71 localStorage.setItem(this.GLOBAL_TRIAL_COUNT_KEY, '0'); 72 localStorage.setItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY, new Date().toISOString()); 73 localStorage.setItem(`trial_fp_${this.fingerprint}`, 'true'); 74 75 // 触åè·¨æ ç¾é¡µåæ¥äºä»¶ 76 this.broadcastTrialUpdate(); 77 78 return true; 79 } 80 81 resetTrialStatus() { 82 localStorage.setItem(this.GLOBAL_TRIAL_KEY, 'false'); 83 localStorage.setItem(this.GLOBAL_TRIAL_COUNT_KEY, '1'); 84 localStorage.removeItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY); 85 86 // æ¸ çæçº¹è®°å½ï¼å¼åæµè¯ç¨ï¼ 87 Object.keys(localStorage).forEach(key => { 88 if (key.startsWith('trial_fp_')) { 89 localStorage.removeItem(key); 90 } 91 }); 92 } 93 94 broadcastTrialUpdate() { 95 // åéèªå®ä¹äºä»¶ï¼éç¥å ¶ä»æ ç¾é¡µ 96 const event = new CustomEvent('grok4TrialUpdate', { 97 detail: { 98 hasTriedBefore: this.hasTriedBefore(), 99 remainingTrials: this.getRemainingTrials(), 100 timestamp: new Date().toISOString() 101 } 102 }); 103 104 window.dispatchEvent(event); 105 106 // ä½¿ç¨ localStorage äºä»¶è¿è¡è·¨æ ç¾é¡µéä¿¡ 107 localStorage.setItem('grok4_trial_sync', JSON.stringify({ 108 action: 'trial_consumed', 109 timestamp: Date.now(), 110 fingerprint: this.fingerprint 111 })); 112 } 113 114 // çå¬å ¶ä»æ ç¾é¡µçè¯ç¨ç¶æåå 115 setupCrossTabSync() { 116 window.addEventListener('storage', (event) => { 117 if (event.key === 'grok4_trial_sync') { 118 try { 119 const data = JSON.parse(event.newValue); 120 if (data.action === 'trial_consumed') { 121 // å ¶ä»æ ç¾é¡µæ¶è´¹äºè¯ç¨æ¬¡æ°ï¼å·æ°å½å页é¢ç¶æ 122 this.refreshTrialStatus(); 123 } 124 } catch (error) { 125 console.warn('Failed to parse trial sync data:', error); 126 } 127 } 128 }); 129 130 // çå¬èªå®ä¹äºä»¶ 131 window.addEventListener('grok4TrialUpdate', (event) => { 132 this.refreshTrialStatus(); 133 }); 134 } 135 136 refreshTrialStatus() { 137 // å·æ°è¯ç¨ç¶ææ¾ç¤º 138 const event = new CustomEvent('refreshTrialStatus', { 139 detail: { 140 hasTriedBefore: this.hasTriedBefore(), 141 remainingTrials: this.getRemainingTrials() 142 } 143 }); 144 145 window.dispatchEvent(event); 146 } 147 148 // è·åè¯ç¨ä½¿ç¨çæ¶é´æ³ 149 getTrialTimestamp() { 150 return localStorage.getItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY); 151 } 152 153 // å¼åè å·¥å ·ï¼éç½®è¯ç¨ç¶æï¼ä» ç¨äºæµè¯ï¼ 154 devResetTrial() { 155 if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') { 156 this.resetTrialStatus(); 157 console.log('Trial status reset for development'); 158 this.refreshTrialStatus(); 159 } 160 } 161} 162 163// å ¨å±å®ä¾ 164window.globalTrialManager = new GlobalTrialManager(); 165window.globalTrialManager.setupCrossTabSync(); 166 167// å¼åè æ§å¶å°å¿«æ·æ¹å¼ 168if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') { 169 window.resetTrial = () => window.globalTrialManager.devResetTrial(); 170 console.log('Development mode: Use resetTrial() in console to reset trial status'); 171} 172 173// Nav dropdown click toggle (mobile & touch support) 174document.addEventListener('DOMContentLoaded', function() { 175 document.querySelectorAll('.nav__dropdown-toggle').forEach(function(toggle) { 176 toggle.addEventListener('click', function(e) { 177 e.preventDefault(); 178 var item = this.closest('.nav__item--dropdown'); 179 var isActive = item.classList.contains('active'); 180 document.querySelectorAll('.nav__item--dropdown').forEach(function(el) { 181 el.classList.remove('active'); 182 }); 183 if (!isActive) item.classList.add('active'); 184 }); 185 }); 186 document.addEventListener('click', function(e) { 187 if (!e.target.closest('.nav__item--dropdown')) { 188 document.querySelectorAll('.nav__item--dropdown').forEach(function(el) { 189 el.classList.remove('active'); 190 }); 191 } 192 }); 193});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.