PageSourceSearch

https://aipick.kr/static/js/visitor_tracking.js

js aipick.kr collected 2026-10-02 01:04:18 UTC 18,962 bytes, 350 lines download raw bytes

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.