// ==UserScript==
// @name KoneGG 확장 검색 시스템 (API 버전, 제목/내용/작성자 검색, 날짜 정렬 지원)
// @namespace http://tampermonkey.net/
// @version 3.1
// @description kone.gg 사이트에서 API를 사용하여 제목, 내용, 작성자명이 특정 키워드를 포함하는 게시글을 검색하고 날짜별로 정렬합니다. (현재 서브 필터링, 새탭 열기 지원)
// @author You
// @match https://kone.gg/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_log
// @downloadURL https://update.greasyfork.icu/scripts/536840/KoneGG%20%ED%99%95%EC%9E%A5%20%EA%B2%80%EC%83%89%20%EC%8B%9C%EC%8A%A4%ED%85%9C%20%28API%20%EB%B2%84%EC%A0%84%2C%20%EC%A0%9C%EB%AA%A9%EB%82%B4%EC%9A%A9%EC%9E%91%EC%84%B1%EC%9E%90%20%EA%B2%80%EC%83%89%2C%20%EB%82%A0%EC%A7%9C%20%EC%A0%95%EB%A0%AC%20%EC%A7%80%EC%9B%90%29.user.js
// @updateURL https://update.greasyfork.icu/scripts/536840/KoneGG%20%ED%99%95%EC%9E%A5%20%EA%B2%80%EC%83%89%20%EC%8B%9C%EC%8A%A4%ED%85%9C%20%28API%20%EB%B2%84%EC%A0%84%2C%20%EC%A0%9C%EB%AA%A9%EB%82%B4%EC%9A%A9%EC%9E%91%EC%84%B1%EC%9E%90%20%EA%B2%80%EC%83%89%2C%20%EB%82%A0%EC%A7%9C%20%EC%A0%95%EB%A0%AC%20%EC%A7%80%EC%9B%90%29.meta.js
// ==/UserScript==
(function() {
'use strict';
// 디버그 로깅
const DEBUG = true;
// 검색 중단 플래그
let searchCancelled = false;
// 현재 검색 결과 데이터 저장용
let currentSearchResultsData = [];
function log(...args) {
if (DEBUG) {
console.log('[KoneGG 검색 API]', ...args);
}
}
// 현재 서브 이름 가져오기
function getCurrentSubName() {
const path = window.location.pathname;
const matches = path.match(/\/s\/([^\/]+)/);
const subName = matches ? matches[1] : null;
log('현재 서브명:', subName);
return subName;
}
// CSS 스타일 추가
GM_addStyle(`
/* 기존 스타일 유지 */
.kone-search-button {
position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px;
border-radius: 50%; background-color: #3b82f6; color: white;
display: flex; align-items: center; justify-content: center;
cursor: pointer; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 9998; transition: all 0.3s ease;
}
.kone-search-button:hover { background-color: #2563eb; transform: scale(1.05); }
.dark .kone-search-button { background-color: #4b5563; }
.dark .kone-search-button:hover { background-color: #374151; }
.kone-search-panel {
position: fixed; bottom: 80px; right: 20px; width: 350px;
background-color: white; border-radius: 8px;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); z-index: 9997;
font-family: 'Pretendard', sans-serif; display: none;
overflow: hidden; border: 1px solid #e5e7eb;
}
.dark .kone-search-panel { background-color: #27272a; border-color: #3f3f46; color: #e4e4e7; }
.kone-search-header {
padding: 15px; border-bottom: 1px solid #e5e7eb;
display: flex; justify-content: space-between; align-items: center;
}
.dark .kone-search-header { border-color: #3f3f46; }
.kone-search-title { font-weight: 600; font-size: 16px; }
.kone-search-close { cursor: pointer; opacity: 0.6; }
.kone-search-close:hover { opacity: 1; }
.kone-search-content { padding: 15px; }
.kone-search-form { display: flex; flex-direction: column; gap: 12px; }
.kone-search-input-container { position: relative; }
.kone-search-input, .kone-search-sort-select { /* 공통 스타일 적용 */
width: 100%; padding: 10px 12px;
border: 1px solid #e5e7eb; border-radius: 6px; font-size: 14px; outline: none;
box-sizing: border-box; /* 패딩과 테두리가 너비에 포함되도록 */
}
.kone-search-input { padding-left: 35px; } /* 아이콘 공간 */
.dark .kone-search-input, .dark .kone-search-sort-select {
background-color: #3f3f46; border-color: #52525b; color: #e4e4e7;
}
.kone-search-input:focus, .kone-search-sort-select:focus { border-color: #3b82f6; }
.kone-search-icon {
position: absolute; left: 10px; top: 50%;
transform: translateY(-50%); color: #9ca3af;
}
.kone-search-options-container { /* 옵션과 정렬을 묶는 컨테이너 */
display: flex; flex-direction: column; gap: 10px;
}
.kone-search-options { display: flex; flex-wrap: wrap; gap: 10px; } /* 기존 옵션 */
.kone-search-option { display: flex; align-items: center; gap: 5px; }
.kone-search-option input[type="checkbox"] { margin: 0; }
.kone-search-option label { font-size: 13px; user-select: none; }
.kone-search-sort-options { display: flex; align-items: center; gap: 8px; }
.kone-search-sort-options label { font-size: 13px; white-space: nowrap; }
.kone-search-sort-select { width: auto; flex-grow: 1; padding: 8px 10px;}
.kone-search-settings { display: flex; justify-content: space-between; align-items: center; margin-top:10px;}
.kone-search-checkbox-container { display: flex; align-items: center; gap: 6px; }
.kone-search-checkbox-label { font-size: 13px; user-select: none; }
.kone-search-button-submit {
padding: 8px 16px; background-color: #3b82f6; color: white;
border: none; border-radius: 6px; font-size: 14px; font-weight: 500;
cursor: pointer; transition: background-color 0.3s;
}
.kone-search-button-submit:hover { background-color: #2563eb; }
.dark .kone-search-button-submit { background-color: #4b5563; }
.dark .kone-search-button-submit:hover { background-color: #374151; }
.kone-search-results { margin-top: 15px; max-height: 350px; overflow-y: auto; display: none; }
.kone-search-results-header {
margin-bottom: 10px; font-size: 14px; font-weight: 600;
display: flex; justify-content: space-between; align-items: center;
}
.kone-search-results-count { color: #6b7280; font-size: 13px; font-weight: normal; }
.dark .kone-search-results-count { color: #a1a1aa; }
.kone-search-results-list { display: flex; flex-direction: column; gap: 8px; }
.kone-search-result-item {
padding: 10px; border: 1px solid #e5e7eb; border-radius: 6px;
cursor: pointer; transition: background-color 0.3s; position: relative;
}
.dark .kone-search-result-item { border-color: #3f3f46; }
.kone-search-result-item:hover { background-color: #f9fafb; }
.dark .kone-search-result-item:hover { background-color: #3f3f46; }
.kone-search-result-item::after {
content: '🔗 새 탭에서 열기'; position: absolute; top: 50%; right: 10px;
transform: translateY(-50%); background-color: rgba(59, 130, 246, 0.1);
color: #3b82f6; padding: 2px 6px; border-radius: 4px;
font-size: 11px; opacity: 0; transition: opacity 0.3s; pointer-events: none;
}
.kone-search-result-item:hover::after { opacity: 1; }
.dark .kone-search-result-item::after { background-color: rgba(75, 85, 99, 0.3); color: #9ca3af; }
.kone-search-result-title {
font-weight: 500; font-size: 14px; margin-bottom: 5px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 80px;
}
.kone-search-result-content {
font-size: 12px; margin-bottom: 5px; color: #6b7280;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 80px;
}
.dark .kone-search-result-content { color: #a1a1aa; }
.kone-search-result-meta {
display: flex; justify-content: space-between; font-size: 12px;
color: #6b7280; padding-right: 80px;
}
.dark .kone-search-result-meta { color: #a1a1aa; }
.kone-search-loading {
display: none; justify-content: center; align-items: center;
padding: 15px 0; flex-direction: column; gap: 10px;
}
.kone-search-spinner {
width: 24px; height: 24px; border: 3px solid #f3f3f3;
border-top: 3px solid #3b82f6; border-radius: 50%;
animation: spin 1s linear infinite;
}
.dark .kone-search-spinner { border-color: #3f3f46; border-top-color: #4b5563; }
.kone-search-progress { font-size: 13px; color: #6b7280; text-align: center; }
.dark .kone-search-progress { color: #a1a1aa; }
.kone-search-debug {
font-size: 11px; color: #9ca3af; margin-top: 5px; max-height: 60px;
overflow-y: auto; background-color: rgba(0,0,0,0.05);
padding: 5px; border-radius: 4px; display: none;
}
.dark .kone-search-debug { background-color: rgba(255,255,255,0.05); color: #a1a1aa; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.kone-search-no-results {
padding: 15px; text-align: center; color: #6b7280;
font-size: 14px; display: none;
}
.dark .kone-search-no-results { color: #a1a1aa; }
.kone-search-cancel-button {
background-color: #ef4444; color: white; border: none; border-radius: 6px;
padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer;
display: none; margin-top: 10px; width: 100%;
}
.kone-search-cancel-button:hover { background-color: #dc2626; }
.kone-search-new-tab-info {
padding: 8px 12px; background-color: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 6px;
font-size: 12px; color: #3b82f6; margin-top: 10px; text-align: center;
}
.dark .kone-search-new-tab-info {
background-color: rgba(75, 85, 99, 0.2);
border-color: rgba(75, 85, 99, 0.3); color: #9ca3af;
}
/* Modal Styles */
.kone-search-modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0,0,0,0.5); z-index: 9999; display: none;
}
.kone-search-modal {
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
background-color: white; padding: 25px; border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2); z-index: 10000;
width: 300px; text-align: center; display: none;
}
.dark .kone-search-modal {
background-color: #2d3748; color: #e2e8f0; border: 1px solid #4a5568;
}
.kone-search-modal-message { margin-bottom: 20px; font-size: 15px; line-height: 1.6; }
.kone-search-modal-button {
padding: 10px 20px; border: none; border-radius: 6px;
background-color: #3b82f6; color: white; font-size: 14px;
font-weight: 500; cursor: pointer; transition: background-color 0.2s;
}
.kone-search-modal-button:hover { background-color: #2563eb; }
.dark .kone-search-modal-button { background-color: #4b5563; }
.dark .kone-search-modal-button:hover { background-color: #374151; }
`);
// DOM 요소 생성
function createElements() {
const searchButton = document.createElement('div');
searchButton.className = 'kone-search-button';
searchButton.innerHTML = `
`;
document.body.appendChild(searchButton);
const searchPanel = document.createElement('div');
searchPanel.className = 'kone-search-panel';
searchPanel.innerHTML = `
검색 결과가 없습니다.
💡 검색 결과를 클릭하면 새 탭에서 열립니다
`;
document.body.appendChild(searchPanel);
return {
searchButton, searchPanel,
searchInput: searchPanel.querySelector('.kone-search-input'),
searchSubmitButton: searchPanel.querySelector('.kone-search-button-submit'),
searchCaseSensitive: searchPanel.querySelector('#kone-search-case-sensitive'),
searchResults: searchPanel.querySelector('.kone-search-results'),
searchResultsList: searchPanel.querySelector('.kone-search-results-list'),
searchResultsCount: searchPanel.querySelector('.kone-search-results-count'),
searchLoading: searchPanel.querySelector('.kone-search-loading'),
searchFoundCount: searchPanel.querySelector('#kone-search-found-count'),
searchNoResults: searchPanel.querySelector('.kone-search-no-results'),
searchCloseButton: searchPanel.querySelector('.kone-search-close'),
searchDebug: searchPanel.querySelector('.kone-search-debug'),
searchCancelButton: searchPanel.querySelector('.kone-search-cancel-button'),
searchTitle: searchPanel.querySelector('#kone-search-title'),
searchContent: searchPanel.querySelector('#kone-search-content'),
searchAuthor: searchPanel.querySelector('#kone-search-author'),
searchNewTabInfo: searchPanel.querySelector('.kone-search-new-tab-info'),
searchSortBy: searchPanel.querySelector('#kone-search-sort-by') // 정렬 드롭다운 추가
};
}
// 디버그 메시지 추가
function addDebugMessage(message) {
if (DEBUG) {
const { searchDebug } = elements;
searchDebug.style.display = 'block';
searchDebug.innerHTML += `${message}
`;
searchDebug.scrollTop = searchDebug.scrollHeight;
}
}
// 모달 알림창 표시 함수
function showModal(message) {
let overlay = document.getElementById('kone-search-modal-overlay');
let modalContent = document.getElementById('kone-search-modal-content');
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'kone-search-modal-overlay';
overlay.className = 'kone-search-modal-overlay';
document.body.appendChild(overlay);
modalContent = document.createElement('div');
modalContent.id = 'kone-search-modal-content';
modalContent.className = 'kone-search-modal';
const messageP = document.createElement('p');
messageP.id = 'kone-search-modal-message';
messageP.className = 'kone-search-modal-message';
const closeButton = document.createElement('button');
closeButton.textContent = '확인';
closeButton.className = 'kone-search-modal-button';
closeButton.onclick = () => {
overlay.style.display = 'none';
modalContent.style.display = 'none';
};
overlay.onclick = () => { // Close on overlay click
overlay.style.display = 'none';
modalContent.style.display = 'none';
};
modalContent.appendChild(messageP);
modalContent.appendChild(closeButton);
document.body.appendChild(modalContent);
}
modalContent.querySelector('#kone-search-modal-message').textContent = message;
overlay.style.display = 'block';
modalContent.style.display = 'block';
if (document.body.classList.contains('dark')) {
modalContent.classList.add('dark');
} else {
modalContent.classList.remove('dark');
}
}
async function performSearch(subName, keyword, options) {
const { isCaseSensitive, searchInTitle, searchInContent, searchInAuthor } = options;
searchCancelled = false;
elements.searchSubmitButton.style.display = 'none';
elements.searchCancelButton.style.display = 'block';
elements.searchLoading.querySelector('#kone-search-found-count').textContent = '0';
let allResults = [];
addDebugMessage(`API 검색 시작: '${keyword}' (${isCaseSensitive ? '대소문자 구분' : '대소문자 무시'}) for sub: ${subName}`);
addDebugMessage(`검색 대상 필터: 제목(${searchInTitle}), 내용(${searchInContent}), 작성자(${searchInAuthor})`);
try {
const apiUrl = "https://api.kone.gg/v0/search/article";
const requestBody = { query: keyword };
const response = await fetch(apiUrl, {
method: "POST",
headers: {
"accept": "*/*",
"accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"cache-control": "no-cache",
"content-type": "application/json",
"pragma": "no-cache",
"priority": "u=1, i",
// "sec-ch-ua": "\"Chromium\";v=\"136\", \"Google Chrome\";v=\"136\", \"Not.A/Brand\";v=\"99\"", // 실제 환경에 맞게 조정될 수 있음
// "sec-ch-ua-mobile": "?0",
// "sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"cookie": "__Secure_Neko=ACqkAAIQUAGW9fehdn1DnT-AWxK9o9sRUAGW3pq1znL9i8ELbQgggUMYIPT32cSo4acdUQeJ0me3Jg16wYVmfeAgT_eYiC9s93dYuJLLmK3Nb8CtHxE2KaBxSZl7bMPp1AHPPAmrSo5v_IMH", // User-provided cookie
"Referer": "https://kone.gg/",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
body: JSON.stringify(requestBody)
});
if (searchCancelled) {
addDebugMessage('API 요청 후 검색이 중단되었습니다.');
throw new Error('Search cancelled by user');
}
if (!response.ok) {
const errorText = await response.text();
addDebugMessage(`API 오류: ${response.status} ${response.statusText}. 응답: ${errorText}`);
throw new Error(`API 요청 실패: ${response.status}`);
}
const apiResults = await response.json();
addDebugMessage(`API로부터 ${apiResults.length}개의 결과 수신`);
const filteredBySub = apiResults.filter(article => article.sub_handle === subName);
addDebugMessage(`${filteredBySub.length}개의 결과가 현재 서브 '${subName}'와 일치합니다.`);
const keywordForCheck = isCaseSensitive ? keyword : keyword.toLowerCase();
const finalResults = filteredBySub.filter(article => {
let titleMatch = false;
if (searchInTitle && article.title) {
const titleText = (isCaseSensitive ? article.title : article.title.toLowerCase());
if (titleText.includes(keywordForCheck) || titleText.includes(`${keywordForCheck}`)) {
titleMatch = true;
}
}
let contentMatch = false;
if (searchInContent && article.content) {
const contentText = (isCaseSensitive ? article.content : article.content.toLowerCase());
if (contentText.includes(keywordForCheck) || contentText.includes(`${keywordForCheck}`)) {
contentMatch = true;
}
}
if (searchInTitle && titleMatch) return true;
if (searchInContent && contentMatch) return true;
if (searchInAuthor) return true;
return false;
});
addDebugMessage(`최종 필터링 후 ${finalResults.length}개의 결과.`);
allResults = finalResults.map(apiArticle => {
return {
article_id: apiArticle.article_id,
title: apiArticle.title || '제목 없음',
content: apiArticle.content || '내용 없음',
url: `https://kone.gg/s/${subName}/${apiArticle.article_id}`,
author: "정보 없음",
date: apiArticle.created_at ? new Date(apiArticle.created_at).toLocaleString('ko-KR') : '날짜 없음',
original_created_at: apiArticle.created_at, // 정렬을 위한 원본 날짜 저장
matchType: 'API 검색'
};
});
elements.searchFoundCount.textContent = allResults.length;
} catch (error) {
if (error.message === 'Search cancelled by user') {
addDebugMessage('검색이 중단되어 결과를 처리하지 않습니다.');
} else {
console.error('API 검색 오류:', error);
addDebugMessage(`API 검색 중 심각한 오류: ${error.message}`);
elements.searchNoResults.textContent = 'API 검색 중 오류가 발생했습니다. 콘솔을 확인하세요.';
elements.searchNoResults.style.display = 'block';
}
allResults = [];
} finally {
elements.searchSubmitButton.style.display = 'block';
elements.searchCancelButton.style.display = 'none';
elements.searchCancelButton.textContent = "검색 중단";
elements.searchCancelButton.disabled = false;
elements.searchLoading.style.display = 'none';
}
return allResults;
}
// 검색 결과 표시 함수
function displaySearchResults(results, sortOrder = 'default') {
const { searchResults, searchResultsList, searchResultsCount, searchNoResults, searchNewTabInfo } = elements;
searchResultsList.innerHTML = ''; // 목록 초기화
// 정렬 적용
let sortedResults = [...results]; // 원본 배열 수정을 피하기 위해 복사
if (sortOrder === 'date_asc') {
sortedResults.sort((a, b) => {
const dateA = a.original_created_at ? new Date(a.original_created_at) : 0;
const dateB = b.original_created_at ? new Date(b.original_created_at) : 0;
return dateA - dateB;
});
} else if (sortOrder === 'date_desc') {
sortedResults.sort((a, b) => {
const dateA = a.original_created_at ? new Date(a.original_created_at) : 0;
const dateB = b.original_created_at ? new Date(b.original_created_at) : 0;
return dateB - dateA;
});
}
// 'default'는 API 반환 순서 (또는 이전 정렬 상태 유지)
if (sortedResults.length === 0) {
searchResults.style.display = 'none';
searchNoResults.style.display = 'block';
searchNewTabInfo.style.display = 'none';
return;
}
searchResultsCount.textContent = `${sortedResults.length}개`;
searchNewTabInfo.style.display = 'block';
sortedResults.forEach(result => {
const resultItem = document.createElement('div');
resultItem.className = 'kone-search-result-item';
resultItem.innerHTML = `
${result.title}
${result.content}
`;
// matchType 제거, API 검색이므로 명확함
resultItem.addEventListener('click', (e) => {
e.preventDefault();
window.open(result.url, '_blank');
log(`새 탭에서 게시글 열기: ${result.url}`);
});
resultItem.style.cursor = 'pointer';
searchResultsList.appendChild(resultItem);
});
searchResults.style.display = 'block';
searchNoResults.style.display = 'none';
}
// 이벤트 핸들러 설정
function setupEventHandlers() {
const {
searchButton, searchPanel, searchInput, searchSubmitButton,
searchCaseSensitive, searchResults, searchLoading, searchNoResults,
searchCloseButton, searchDebug, searchCancelButton,
searchTitle, searchContent, searchAuthor, searchNewTabInfo, searchSortBy
} = elements;
let isPanelVisible = false;
searchButton.addEventListener('click', () => {
isPanelVisible = !isPanelVisible;
searchPanel.style.display = isPanelVisible ? 'block' : 'none';
if (isPanelVisible) searchInput.focus();
});
searchCloseButton.addEventListener('click', () => {
searchPanel.style.display = 'none';
isPanelVisible = false;
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') searchSubmitButton.click();
});
searchCancelButton.addEventListener('click', () => {
searchCancelled = true;
searchCancelButton.textContent = "검색 중단 중...";
searchCancelButton.disabled = true;
});
searchSubmitButton.addEventListener('click', async () => {
const keyword = searchInput.value.trim();
const isCaseSensitive = searchCaseSensitive.checked;
const subName = getCurrentSubName();
const searchInTitle = searchTitle.checked;
const searchInContent = searchContent.checked;
const searchInAuthor = searchAuthor.checked;
const currentSortOrder = searchSortBy.value;
if (!keyword) {
showModal('검색어를 입력해주세요.'); return;
}
if (!subName) {
showModal('서브 페이지에서만 검색이 가능합니다. (예: /s/서브이름/)'); return;
}
if (!searchInTitle && !searchInContent && !searchInAuthor) {
showModal('제목, 내용, 작성자 중 하나 이상 선택해주세요.'); return;
}
searchResults.style.display = 'none';
searchNoResults.style.display = 'none';
searchNewTabInfo.style.display = 'none';
searchLoading.style.display = 'flex';
searchDebug.innerHTML = '';
if (DEBUG) searchDebug.style.display = 'block';
try {
const searchOptions = {
isCaseSensitive, searchInTitle, searchInContent, searchInAuthor
};
const results = await performSearch(subName, keyword, searchOptions);
currentSearchResultsData = [...results]; // 새로운 검색 결과로 업데이트
displaySearchResults(currentSearchResultsData, currentSortOrder);
} catch (error) {
console.error('검색 처리 오류:', error);
addDebugMessage(`심각한 오류: ${error.message}`);
showModal('검색 중 오류가 발생했습니다.');
} finally {
searchLoading.style.display = 'none';
searchCancelButton.textContent = "검색 중단";
searchCancelButton.disabled = false;
searchCancelButton.style.display = 'none';
searchSubmitButton.style.display = 'block';
}
});
// 정렬 옵션 변경 시 이벤트 리스너
searchSortBy.addEventListener('change', () => {
if (currentSearchResultsData.length > 0) {
const newSortOrder = searchSortBy.value;
addDebugMessage(`정렬 변경: ${newSortOrder}`);
displaySearchResults(currentSearchResultsData, newSortOrder); // 이미 저장된 데이터로 재정렬 및 표시
}
});
document.addEventListener('click', (e) => {
if (isPanelVisible && !searchPanel.contains(e.target) && !searchButton.contains(e.target)) {
const modalContent = document.getElementById('kone-search-modal-content');
if (modalContent && modalContent.contains(e.target)) {
return;
}
searchPanel.style.display = 'none';
isPanelVisible = false;
}
});
}
let elements;
function init() {
elements = createElements();
window.elements = elements;
setupEventHandlers();
log('KoneGG 확장 검색 시스템 (API + 날짜 정렬)이 초기화되었습니다.');
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
init();
} else {
window.addEventListener('load', init);
}
})();