/* 방문 신호 (2026-11-03) — 페이지가 화면에 다 뜬 뒤(load) /api/visit 로 현재 경로를 1회 보낸다. 공통 푸터(_footer.html)에서 읽으므로 공개 페이지(_layout·home·about·contact)에만 붙는다. 관리자 화면·404 안내 화면에는 없다. 스크립트를 실행하지 않는 봇은 이 신호를 보내지 않아 방문 통계에서 빠진다. 거르기·중복 무시는 서버(app.py api_visit)가 한다. sendBeacon 은 text/plain 문자열로 보낸다(미리 묻는 CORS 요청 없음). 없거나 실패하면 fetch keepalive. 실패해도 화면에는 영향 없음. 2026-11-03 오후 추가: 사람이 실제로 본 페이지만 센다. - Chrome 주소창 자동완성·링크 미리 불러오기(prerender)는 사용자가 열지 않은 페이지도 뒤에서 열어 스크립트를 실행한다 → document.prerendering 이면 실제로 화면에 나올 때(prerenderingchange)까지 기다린다. - 백그라운드 탭(화면에 한 번도 안 보인 상태)도 보일 때까지 기다린다(visibilitychange). */ (function () { var sent = false; function send() { if (sent) return; sent = true; var body = JSON.stringify({ path: location.pathname }); try { if (navigator.sendBeacon && navigator.sendBeacon("/api/visit", body)) return; } catch (e) { /* 아래 fetch 로 */ } try { fetch("/api/visit", { method: "POST", body: body, keepalive: true, credentials: "same-origin", headers: { "Content-Type": "text/plain;charset=UTF-8" } }).catch(function () {}); } catch (e) { /* 기록 실패는 무시 */ } } function sendWhenVisible() { if (document.prerendering) { document.addEventListener("prerenderingchange", sendWhenVisible, { once: true }); return; } if (document.visibilityState === "visible") { send(); return; } document.addEventListener("visibilitychange", function onVis() { if (document.visibilityState === "visible") { document.removeEventListener("visibilitychange", onVis); send(); } }); } if (document.readyState === "complete") sendWhenVisible(); else window.addEventListener("load", sendWhenVisible, { once: true }); })();