1document.addEventListener('DOMContentLoaded', function() { 2 // ë¡ì»¬ì¤í ë¦¬ì§ ëª¨ë¬ ë°ì´í° ì´ê¸°í í¨ì 3 const resetModalStorage = () => { 4 localStorage.removeItem('popupShown'); // ì´ ê°ì ë ì´ì ì§ì ì¬ì©ëì§ ìì ì ììµëë¤. 5 localStorage.removeItem('doNotShowToday'); 6 console.log('ëª¨ë¬ ê´ë ¨ ë¡ì»¬ì¤í 리ì§ê° ì´ê¸°íëììµëë¤.'); 7 }; 8 9 // ê°ë° ì¤ í ì¤í¸ë¥¼ ìí´ ìë ì¤ì 주ìì í´ì íë©´ íì´ì§ ë¡ëìë§ë¤ ëª¨ë¬ ë°ì´í°ê° ì´ê¸°íë©ëë¤. 10 // resetModalStorage(); 11 12 // window ê°ì²´ì ì´ê¸°í í¨ì ë ¸ì¶ (ì½ììì ì ê·¼ ê°ë¥) 13 window.resetVisitorModal = resetModalStorage; 14 15 // 방문ì ì ë³´ ìì² ë° ì²ë¦¬ í¨ì (ìì : ì´ë²¤í¸ íì í¸ì¶ ë¡ì§ ì¶ê°) 16 const fetchVisitorInfo = () => { 17 fetch('/visitor/register', { 18 method: 'POST', 19 headers: { 20 'Content-Type': 'application/json', 21 } 22 }) 23 .then(response => { 24 if (!response.ok) { 25 throw new Error(`HTTP error! status: ${response.status}`); 26 } 27 return response.json(); 28 }) 29 .then(data => { 30 console.log('Visitor register response:', data); 31 32 // ë´ì¼ë¡ íì ë ê²½ì° ì²ë¦¬ ì¤ë¨ 33 if (data.is_bot) { 34 console.log('Bot detected, skipping visitor tracking.'); 35 return; 36 } 37 38 if (data.visitor_number) { 39 localStorage.setItem('visitorNumber', data.visitor_number); 40 } else { 41 console.warn('Visitor number not received from server.'); 42 } 43 44 // 방문ì ì ë°°ë ì ë°ì´í¸ 45 updateVisitorCount(); 46 47 // ì´ë²¤í¸ íì íì ì¬ë¶ íì¸ ë° íì 먼ì í¸ì¶ 48 if (data.effective_visit_date) { 49 // checkAndShowEventPopup(data.effective_visit_date); // ì´ë²¤í¸ íì í¸ì¶ 주ì ì²ë¦¬ 50 } else { 51 console.log('Effective date is missing, cannot reliably check event popup display.'); 52 } 53 54 // 방문ì ìì íì íì ì¬ë¶ íì¸ ë° íì ëì¤ì í¸ì¶ 55 // ìì¸ ë¬¸ì ë¡ ìì ë¹íì±í 56 // if (data.visitor_number && data.effective_visit_date) { 57 // checkPopupDisplay(data.visitor_number, data.effective_visit_date); 58 // } else { 59 // console.log('Visitor number or effective date is missing, cannot check visitor rank popup display.'); 60 // } 61 62 }) 63 .catch(error => { 64 console.error('방문ì ì ë³´ ìì²/ì²ë¦¬ ì¤í¨:', error); 65 updateVisitorCount(); 66 }); 67 }; 68 69 // 방문ì ì ì ë°ì´í¸ í¨ì (ë´ íí°ë§ ì¶ê°) 70 const updateVisitorCount = () => { 71 fetch('/visitor/count') 72 .then(response => response.json()) 73 .then(data => { 74 // ë´ì¼ë¡ íì ë ê²½ì° ë°°ë ì¨ê¸°ê¸° 75 if (data.is_bot) { 76 console.log('Bot detected, hiding visitor banner.'); 77 const visitorBanner = document.getElementById('visitor-count-banner'); 78 if (visitorBanner) { 79 visitorBanner.style.display = 'none'; 80 } 81 return; 82 } 83 84 const visitorBanner = document.getElementById('visitor-count-banner'); 85 if (visitorBanner) { 86 visitorBanner.innerHTML = `(${data.display_date} 5:00AM 기ì¤) <span class="text-primary font-bold underline decoration-primary decoration-2">${data.count}ëª </span>ì´ ìì ëê°ê³ ìì´ ðð»`; 87 visitorBanner.style.display = 'block'; 88 } 89 }) 90 .catch(error => { 91 console.error('방문ì ì ì¡°í ì¤í¨:', error); 92 }); 93 }; 94 95 // íì íì ì¬ë¶ íì¸ í¨ì (ìë² ê¸°ì¤ ë ì§ ì¬ì©íëë¡ ìì ) 96 const checkPopupDisplay = (visitorNumber, effectiveVisitDate) => { 97 const doNotShowTodayStoredDate = localStorage.getItem('doNotShowToday'); 98 99 // localStorageì ì ì¥ë ë ì§(1ì£¼ì¼ í)ì ìë²ê° ìë ¤ì¤ '방문 ê¸°ì¤ ë ì§'를 ë¹êµ 100 if (!doNotShowTodayStoredDate || doNotShowTodayStoredDate <= effectiveVisitDate) { 101 // showVisitorPopup í¸ì¶ ì effectiveVisitDate ì ë¬ 102 showVisitorPopup(visitorNumber, effectiveVisitDate); 103 } else { 104 console.log(`Popup not shown. Reason: doNotShowToday date (${doNotShowTodayStoredDate}) is greater than effective visit date (${effectiveVisitDate}).`); 105 } 106 }; 107 108 // íì íì í¨ì ('ì¤ë í루 ì ë¨ê² í기' ë¡ì§ ìì + 모ë ë«ê¸° ë²í¼ì ì ì© + ì´ë¯¸ì§ í¬ê¸° ì¡°ì ) 109 const showVisitorPopup = (visitorNumber, effectiveVisitDate) => { // effectiveVisitDate ì¸ì ì¶ê° 110 // 기존 ëª¨ë¬ ì ê±° (ì¤ë³µ íì ë°©ì§) 111 const existingModal = document.getElementById('visitor-modal'); 112 if (existingModal) { 113 existingModal.remove(); 114 } 115 116 const modal = document.createElement('div'); 117 modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; 118 modal.id = 'visitor-modal'; 119 120 // ì´ë¯¸ì§ í¬ê¸° w-24 h-24 => w-48 h-48 ë¡ ë³ê²½ 121 modal.innerHTML = ` 122 <div class="bg-white rounded-xl w-full max-w-md mx-4 overflow-hidden shadow-xl transform transition-all"> 123 <div class="relative"> 124 <button id="close-visitor-modal" class="absolute right-4 top-4 text-gray-500 hover:text-gray-800"> 125 <i class="fa-solid fa-times h-6 w-6"></i> 126 </button> 127 </div> 128 <div class="p-6 text-center"> 129 <img src="/static/images/run_modal_icon.png" alt="ë¬ë¦¬ê¸° ìì´ì½" class="w-48 h-48 mx-auto mb-4"> 130 <h3 class="text-xl mb-1">ì¤ë <span class="font-bold underline decoration-primary decoration-2">${visitorNumber}ë±ì¼ë¡</span> ì ì¥íì´!</h3> 131 <p class="text-gray-600 mb-6">AI ë´ì¤ ì½ê³ ìì ëê°ìð¥</p> 132 <div class="flex justify-center gap-4 mb-4"> 133 <button id="close-visitor-button" class="bg-gray-100 text-gray-700 px-4 py-2 rounded-full font-medium hover:bg-gray-200 transition-colors">ë«ê¸°</button> 134 <button id="go-ahead-button" class="bg-primary text-black px-4 py-2 rounded-full font-medium hover:bg-opacity-90 transition-colors">ìì ê°ê¸°</button> 135 </div> 136 <div class="text-center mt-2"> 137 <button id="do-not-show-today" class="text-gray-500 text-sm underline">í ë¬ ëì ì ë¨ê² í기</button> 138 </div> 139 </div> 140 </div> 141 `; 142 143 document.body.appendChild(modal); 144
145 // ê³µíµ ë«ê¸° ë¡ì§: effectiveVisitDate + 7ì¼ì localStorageì ì ì¥íê³ ëª¨ë¬ ì ê±° 146 const closeAndSetDoNotShow = () => { 147 if (effectiveVisitDate) { 148 // 1ì£¼ì¼ í ë ì§ ê³ì° 149 const baseDate = new Date(effectiveVisitDate); 150 baseDate.setDate(baseDate.getDate() + 7); 151 const oneWeekLater = baseDate.toISOString().split('T')[0]; 152 153 localStorage.setItem('doNotShowToday', oneWeekLater); 154 console.log(`'doNotShowToday' set for date: ${oneWeekLater} (1 week after ${effectiveVisitDate})`); 155 } else { 156 // effectiveVisitDateê° ìë ë¹ì ìì ì¸ ê²½ì° ëë¹ (ì°¨ì ì± ) 157 console.warn("Effective visit date not available when closing modal. Using client date as fallback."); 158 const today = new Date(); 159 today.setDate(today.getDate() + 7); 160 const oneWeekLater = today.toISOString().split('T')[0]; 161 localStorage.setItem('doNotShowToday', oneWeekLater); 162 } 163 modal.remove(); 164 }; 165 166 // ëª¨ë¬ ë«ê¸° ì´ë²¤í¸ ì¤ì (모ë ë«ê¸°/ëì ë²í¼ì ê³µíµ ë¡ì§ ì ì©) 167 document.getElementById('close-visitor-modal').addEventListener('click', closeAndSetDoNotShow); 168 169 document.getElementById('close-visitor-button').addEventListener('click', closeAndSetDoNotShow); 170 171 document.getElementById('do-not-show-today').addEventListener('click', closeAndSetDoNotShow); 172 173 // 'ìì ê°ê¸°' ë²í¼ ì´ë²¤í¸ 리ì¤ë ì¶ê° 174 document.getElementById('go-ahead-button').addEventListener('click', closeAndSetDoNotShow); 175 176 // ëª¨ë¬ ì¸ë¶ í´ë¦ ì ë«ê¸° (ì´ ê²½ì°ë doNotShowToday ì¤ì ì í¨) 177 modal.addEventListener('click', (e) => { 178 if (e.target === modal) { 179 modal.remove(); // ê·¸ë¥ ë«ê¸°ë§ í¨ 180 } 181 }); 182 }; 183 184 // ì´ë²¤í¸ ì¤ì 185 const EVENT_DATE_STR = '2025-04-30'; 186 const EVENT_START_HOUR = 20; // ì¤í 8ì 187 const EVENT_END_HOUR = 21; // ì¤í 9ì 188 const EVENT_NOTICE_URL = 'https://aipick.kr/community/notice/37'; // 미ë¤ë¥´ë° íìì´ë? ë§í¬ 189 190 // ìë² ìê° ê¸°ì¤ ëë í´ë¼ì´ì¸í¸ ìê° ê¸°ì¤ 'ì¤ë' ë ì§ ê°ì ¸ì¤ê¸° (íì ì²´í¬ì©) 191 // TODO: ìë² ìê° ê¸°ì¤ì´ íìíë¤ë©´ fetchVisitorInfo ìëµì effective_visit_date íì© íì 192 const getEffectiveCheckDateForEvent = () => { 193 // íì¬ë í´ë¼ì´ì¸í¸ ìê° ê¸°ì¤ì¼ë¡ 구í (YYYY-MM-DD íì) 194 // íìì 기존 getEffectiveCheckDate í¨ìë ìë² ìëµ íì© ë¡ì§ì¼ë¡ ìì 195 return new Date().toISOString().split('T')[0]; 196 }; 197 198 // ì´ë²¤í¸ íì íì ì¬ë¶ íì¸ ë° í¸ì¶ í¨ì (ìì : ìë² ê¸°ì¤ ë ì§ ì¬ì©) 199 const checkAndShowEventPopup = (effectiveVisitDate) => { // ìë² ê¸°ì¤ ë ì§ ë°ê¸° 200 const now = new Date(); // íì¬ ìê° (ì´ë²¤í¸ ì¢ ë£ ìê° ì²´í¬ì©) 201 const eventDate = new Date(EVENT_DATE_STR); 202 eventDate.setHours(0, 0, 0, 0); 203 204 // ìë² ê¸°ì¤ ë ì§ë¥¼ Date ê°ì²´ë¡ íì± 205 const serverToday = new Date(effectiveVisitDate); 206 serverToday.setHours(0, 0, 0, 0); // ìê° ì´ê¸°í 207 208 const diffTime = eventDate.getTime() - serverToday.getTime(); // ìë² ê¸°ì¤ ì¤ë ë ì§ì ë¹êµ 209 const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 210 211 const doNotShowTodayStoredDate = localStorage.getItem('eventPopupDoNotShowToday'); 212 213 console.log(`Event D-day calculation (based on ${effectiveVisitDate}): diffDays = ${diffDays}`); 214 215 const isEventDay = (diffDays === 0); 216 const isBeforeEventEnd = !(isEventDay && now.getHours() >= EVENT_END_HOUR); 217 218 // ì¡°ê±´ ìì : effectiveVisitDateì localStorage ë¹êµ 219 if (diffDays >= 0 && diffDays <= 7 && doNotShowTodayStoredDate !== effectiveVisitDate && isBeforeEventEnd) { 220 showEventPopup(diffDays, isEventDay, effectiveVisitDate); // effectiveVisitDate ì ë¬ 221 } else if (doNotShowTodayStoredDate === effectiveVisitDate) { 222 console.log(`Event Popup not shown. Reason: doNotShowToday date (${doNotShowTodayStoredDate}) matches effective visit date (${effectiveVisitDate}).`); 223 } else if (!isBeforeEventEnd) { 224 console.log('Event Popup not shown. Event time has passed for today.'); 225 } else { 226 console.log('Event Popup not shown. Not within D-7 period or other reason.'); 227 } 228 }; 229 230 // ì´ë²¤í¸ íì ìì± ë° íì í¨ì (ìì : ì´ë¯¸ì§ ì¤ë²ë ì´, ë²í¼ ë¡ì§, ìë² ê¸°ì¤ ë ì§ ì¬ì©) 231 const showEventPopup = (remainingDays, isEventDay, effectiveVisitDate) => { // effectiveVisitDate ì¸ì ì¶ê° 232 const existingModal = document.getElementById('event-modal'); 233 if (existingModal) { 234 existingModal.remove(); 235 } 236 237 const modal = document.createElement('div'); 238 modal.className = 'fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 p-4'; 239 modal.id = 'event-modal'; 240 241 let headerText = "미ë¤ë¥´ë° íìê°ì ì´ë²¤í¸"; 242 let imageContainerHTML = ''; // ì´ë¯¸ì§ì ì¤ë²ë ì´ í ì¤í¸ë¥¼ ë´ì HTML 243 let dateText = `ì¼ì: 2025ë 4ì 30ì¼ ì¤í 8~9ì`; 244 let button1Text = "ë«ê¸°"; 245 let actionButtonHTML = ''; // ë ë²ì§¸ ë²í¼ HTML (a ëë button) 246
247 const dDayImageSrc = '/static/images/event/dday.png'; // ë¹ì¼ ì´ë¯¸ì§ 248 const dnDayImageSrc = '/static/images/event/dnday.png'; // D-n ì´ë¯¸ì§ 249 250 if (remainingDays > 0) { 251 // D-n: dnday.png ìì í ì¤í¸ ì¤ë²ë ì´ 252 headerText = "미ë¤ë¥´ë° íìê°ì ì´ë²¤í¸"; // í¤ëìë D-n ì ê±° (ì´ë¯¸ì§ ìì íì) 253 imageContainerHTML = ` 254 <div class="relative inline-block mx-auto mb-4"> 255 <img src="${dnDayImageSrc}" alt="ì´ë²¤í¸ D-${remainingDays}" class="block max-w-full max-h-48"> 256 <span class="absolute top-1/4 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-red-600 text-4xl font-bold" style="text-shadow: 1px 1px 2px white;"> 257 D-${remainingDays} 258 </span> 259 </div> 260 `; 261 // D-n: 'ì´ë²¤í¸ íì¸í기' ë²í¼ (ê³µì§ ë§í¬) 262 actionButtonHTML = `<a href="https://aipick.kr/community/notice/44" id="close-event-news-button" class="bg-primary text-black px-6 py-2 rounded-full font-medium hover:bg-opacity-90 transition-colors no-underline">ì´ë²¤í¸ íì¸í기</a>`; 263 } else if (isEventDay) { 264 const now = new Date();
264 // íì¬ ìê° ê°ì ¸ì¤ê¸° 265 const currentHour = now.getHours(); 266 267 // D-day: dday.png ì¬ì© 268 headerText = "미ë¤ë¥´ë° íìê°ì ì´ë²¤í¸"; // ë¹ì¼ í¤ë 269 dateText = `ì¼ì: ì¤ë ì¤í 8~9ì`; 270 imageContainerHTML = ` 271 <div class="mx-auto mb-4"> 272 <img src="${dDayImageSrc}" alt="ì´ë²¤í¸ ë¹ì¼" class="block max-w-full max-h-48 mx-auto"> 273 </div> 274 `; 275 // D-day ìê°ëë³ ë²í¼ ë¶ê¸° 276 if (currentHour >= EVENT_START_HOUR) { // ì¤í 8ì ì´í (20ì í¬í¨) 277 // D-day ì¤í 8ì ì´í: 'íìê°ì íë¬ê°ê¸°' ë²í¼ (ë§í¬) 278 actionButtonHTML = `<a href="/join" id="action-event-button" class="bg-primary text-black px-6 py-2 rounded-full font-medium hover:bg-opacity-90 transition-colors no-underline">íìê°ì íë¬ê°ê¸°</a>`; 279 } else { // ì¤í 8ì ì´ì 280 // D-day ì¤í 8ì ì´ì : 'ì´ë²¤í¸ íì¸í기' ë²í¼ (ê³µì§ ë§í¬) 281 actionButtonHTML = `<a href="https://aipick.kr/community/notice/44" id="close-event-news-button" class="bg-primary text-black px-6 py-2 rounded-full font-medium hover:bg-opacity-90 transition-colors no-underline">ì´ë²¤í¸ íì¸í기</a>`; 282 } 283 } 284 285 modal.innerHTML = ` 286 <div class="bg-white rounded-xl w-full max-w-md mx-4 overflow-hidden shadow-xl transform transition-all"> 287 <div class="relative"> 288 <button id="close-event-modal" class="absolute right-4 top-4 text-gray-500 hover:text-gray-800 z-10"> 289 <i class="fa-solid fa-times h-6 w-6"></i> 290 </button> 291 </div> 292 <div class="p-6 pt-8 text-center"> 293 <h2 class="text-2xl font-bold mb-4">${headerText}</h2> 294 ${imageContainerHTML} 295 <p class="text-lg mb-1 font-medium">${dateText}</p> 296 <p class="text-sm text-gray-600 mb-6">(ì ì¼ì ì´íìë ì¼ë° íìì¼ë¡ ê°ì ë©ëë¤.)</p> 297 <div class="flex justify-center gap-4 mb-4"> 298 <button id="close-event-button" class="bg-gray-100 text-gray-700 px-6 py-2 rounded-full font-medium hover:bg-gray-200 transition-colors">${button1Text}</button> 299 ${actionButtonHTML} 300 </div> 301 <div class="text-center mt-2 mb-4"> 302 <button id="event-do-not-show-today" class="hidden text-gray-500 text-sm underline">ì¤ë í루 ì 보기</button> 303 </div> 304 <div class="text-center mt-4 mb-2"> 305 <a href="${EVENT_NOTICE_URL}" target="_blank" rel="noopener noreferrer" id="minerva-info-link" class="text-gray-500 text-sm underline hover:text-gray-700">미ë¤ë¥´ë° íìì´ë?</a> 306 </div> 307 </div> 308 </div> 309 `; 310 311 document.body.appendChild(modal); 312 313 // 'ì¤ë í루 ì 보기' ë° ë«ê¸° ê³µíµ ë¡ì§ (ìì : effectiveVisitDate ì§ì ì¬ì©) 314 const closeAndSetDoNotShowEvent = () => { 315 localStorage.setItem('eventPopupDoNotShowToday', effectiveVisitDate); // ìë² ê¸°ì¤ ë ì§ ì ì¥ 316 console.log(`'eventPopupDoNotShowToday' set for date: ${effectiveVisitDate}`); 317 modal.remove(); 318 }; 319 320 // ì´ë²¤í¸ 리ì¤ë ì¤ì 321 document.getElementById('close-event-modal').addEventListener('click', closeAndSetDoNotShowEvent); 322 document.getElementById('close-event-button').addEventListener('click', closeAndSetDoNotShowEvent); 323 document.getElementById('event-do-not-show-today').addEventListener('click', closeAndSetDoNotShowEvent); 324 325 // D-n 기ê°ì 'ë´ì¤ 보기' ë²í¼ 리ì¤ë ì¶ê° (ëª¨ë¬ ë«ê¸°) 326 const closeNewsButton = document.getElementById('close-event-news-button'); 327 if (closeNewsButton) { 328 closeNewsButton.addEventListener('click', closeAndSetDoNotShowEvent); 329 } 330 331 // "미ë¤ë¥´ë° íìì´ë?" ë§í¬ í´ë¦ ì íì ë«ê³ 'ì¤ë í루 ì 보기' ì¤ì 332 const minervaInfoLink = document.getElementById('minerva-info-link'); 333 if (minervaInfoLink) { 334 minervaInfoLink.addEventListener('click', closeAndSetDoNotShowEvent); 335 } 336 337 // D-dayì 'íìê°ì íë¬ê°ê¸°' ë²í¼ì <a>
337 íê·¸ì´ë¯ë¡ ë³ë 리ì¤ë ë¶íì (í´ë¦ ì ë§í¬ ì´ë) 338 // ë§ì½ ë§í¬ ì´ë íìë 'ì¤ë í루 ì 보기'를 ì¤ì í´ì¼ íë¤ë©´ ì¶ê° ë¡ì§ íì (íì¬ë ì¤ì ì í¨) 339 340 // ëª¨ë¬ ì¸ë¶ í´ë¦ ì ë«ê¸° (DoNotShow ì¤ì ì í¨) 341 modal.addEventListener('click', (e) => { 342 if (e.target === modal) { 343 modal.remove(); 344 } 345 }); 346 }; 347 348 // íì´ì§ ë¡ë ì 방문ì ì ë³´ ìì² ì¤í 349 fetchVisitorInfo(); 350});
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.