(function () { "use strict"; const data = window.GPT_SUSI_2028_WORKBENCH || { meta: {}, changes: [], focusPrograms: { rows: [] }, universities: [] }; const resultData = window.GPT_SUSI_RESULTS_2026 || { rows: [] }; const PAGE_SIZE = 28; const SIGNALS = ["교과", "학종", "논술", "수능최저", "면접", "정시", "반영과목", "5등급"]; const FIVE_GRADE_REFERENCE_POINTS = [ [1, 1.39], [1.083, 1.53], [1.167, 1.73], [1.25, 1.87], [1.333, 2.03], [1.417, 2.18], [1.5, 2.31], [1.583, 2.45], [1.667, 2.61], [1.75, 2.73], [1.833, 2.88], [1.917, 3], [2, 3.16], [2.083, 3.28], [2.167, 3.41], [2.25, 3.54], [2.333, 3.68], [2.417, 3.8], [2.5, 3.95], [2.583, 4.08], [2.667, 4.21], [2.75, 4.34], [2.833, 4.48], [2.917, 4.61], [3, 4.75], ]; const params = new URLSearchParams(window.location.search); const state = { mode: localStorage.getItem("gpt_susi_2028_mode") || "work", view: ["university", "department", "admission"].includes(params.get("view")) ? params.get("view") : "university", universityQuery: params.get("university") || "", query: params.get("q") || "", group: params.get("group") || "all", region: "전체", signal: "전체", visible: PAGE_SIZE, changeQuery: "", changeCategory: "전체", }; let basket = readStorage("gpt_susi_2028_basket", []); let student = readStorage("gpt_susi_2028_student", { name: "", school: "", grade: "", interest: "" }); const universityById = new Map(data.universities.map((university) => [university.id, university])); const elements = { heroMetrics: document.getElementById("heroMetrics"), consultStrip: document.getElementById("consultStrip"), studentName: document.getElementById("studentName"), studentSchool: document.getElementById("studentSchool"), studentGrade: document.getElementById("studentGrade"), studentInterest: document.getElementById("studentInterest"), universitySearch: document.getElementById("universitySearch"), keywordSearch: document.getElementById("keywordSearch"), priorityFilter: document.getElementById("priorityFilter"), regionFilter: document.getElementById("regionFilter"), signalFilter: document.getElementById("signalFilter"), activeFilter: document.getElementById("activeFilter"), resultTitle: document.getElementById("resultTitle"), resultSummary: document.getElementById("resultSummary"), resultList: document.getElementById("resultList"), emptyState: document.getElementById("emptyState"), loadMore: document.getElementById("loadMore"), changeModal: document.getElementById("changeModal"), changeSearch: document.getElementById("changeSearch"), changeCategories: document.getElementById("changeCategories"), changeList: document.getElementById("changeList"), detailModal: document.getElementById("detailModal"), detailModalKicker: document.getElementById("detailModalKicker"), detailModalTitle: document.getElementById("detailModalTitle"), detailModalSubtitle: document.getElementById("detailModalSubtitle"), detailModalBody: document.getElementById("detailModalBody"), focusModal: document.getElementById("focusModal"), focusModalTitle: document.getElementById("focusModalTitle"), focusModalBody: document.getElementById("focusModalBody"), basket: document.getElementById("basket"), basketDim: document.getElementById("basketDim"), basketCount: document.getElementById("basketCount"), basketStudent: document.getElementById("basketStudent"), basketList: document.getElementById("basketList"), }; function readStorage(key, fallback) { try { return JSON.parse(localStorage.getItem(key)) || fallback; } catch (_error) { return fallback; } } function saveStorage(key, value) { localStorage.setItem(key, JSON.stringify(value)); } function escapeHtml(value) { return String(value ?? "").replace(/[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'", }[character])); } function normalise(value) { return String(value || "").normalize("NFKC").toLowerCase().replace(/\s+/g, "").replace(/[()[\]{}·ㆍ,._\-/]/g, ""); } function queryTerms() { return state.query.trim().toLowerCase().split(/\s+/).filter(Boolean); } function groupMatches(university) { if (state.group === "seoulGyeonggi") return university.priorityTags.seoulGyeonggi; if (state.group === "medical") return university.priorityTags.medical; if (state.group === "flagship") return university.priorityTags.flagship; if (state.group === "structured") return university.detailReady; return true; } function universityMatches(university, includeKeyword = true) { const universityQuery = state.universityQuery.trim().toLowerCase(); if (universityQuery && ![university.name, university.short, university.regions.join(" ")].join(" ").toLowerCase().includes(universityQuery)) return false; if (!groupMatches(university)) return false; if (state.region !== "전체" && !university.regions.includes(state.region)) return false; if (state.signal !== "전체" && !university.signals[state.signal]) return false; if (includeKeyword && state.query.trim()) { const terms = queryTerms(); const haystack = [ university.name, university.short, university.regions.join(" "), ...university.admissions.flatMap((admission) => [admission.name, admission.category, admission.selection, admission.ratio, admission.suneung, admission.naesin, admission.interview, admission.note]), ...university.departments.flatMap((department) => [department.college, department.name, department.track, department.note]), ].join(" ").toLowerCase(); if (!terms.every((term) => haystack.includes(term))) return false; } return true; } function structuredDepartmentMatches(university, department) { const terms = queryTerms(); if (!terms.length) return true; const admissionsById = Object.fromEntries(university.admissions.map((admission) => [admission.id, admission])); const text = [department.college, department.name, department.track, department.note, ...Object.keys(department.quota || {}).flatMap((id) => { const admission = admissionsById[id] || {}; return [admission.name, admission.category, admission.ratio, admission.suneung, admission.naesin, admission.interview]; })].join(" ").toLowerCase(); return terms.every((term) => text.includes(term)); } function updateUrl() { const url = new URL(window.location.href); [["view", state.view], ["university", state.universityQuery], ["q", state.query], ["group", state.group === "all" ? "" : state.group]].forEach(([key, value]) => { if (value) url.searchParams.set(key, value); else url.searchParams.delete(key); }); window.history.replaceState({}, "", url); } function renderHero() { const meta = data.meta; elements.heroMetrics.innerHTML = [ [meta.universityCount, "전체 대학"], [meta.groupCounts.seoulGyeonggi, "서울·경기"], [meta.groupCounts.medical, "의·치·약·수·한"], [meta.groupCounts.flagship, "지거국"], ].map(([value, label]) => `
${Number(value).toLocaleString("ko-KR")}${label}
`).join(""); const changeCount = document.querySelector('[data-action="changes"] b'); if (changeCount) changeCount.textContent = `${Number(meta.changeCount || 0).toLocaleString("ko-KR")}건`; } function renderFilters() { const groups = [ ["all", "전체 대학", data.meta.universityCount], ["seoulGyeonggi", "서울·경기", data.meta.groupCounts.seoulGyeonggi], ["medical", "메디컬", data.meta.groupCounts.medical], ["flagship", "지거국", data.meta.groupCounts.flagship], ["structured", "전형 입력 완료", data.meta.detailUniversityCount], ]; elements.priorityFilter.innerHTML = groups.map(([id, label, count]) => ``).join(""); const regions = ["전체", ...new Set(data.universities.flatMap((university) => university.regions))]; elements.regionFilter.innerHTML = regions.map((region) => ``).join(""); elements.signalFilter.innerHTML = ["전체", ...SIGNALS].map((signal) => ``).join(""); const active = []; if (state.group !== "all") active.push(groups.find(([id]) => id === state.group)?.[1]); if (state.region !== "전체") active.push(state.region); if (state.signal !== "전체") active.push(state.signal); if (state.universityQuery) active.push(`대학: ${state.universityQuery}`); if (state.query) active.push(`검색: ${state.query}`); elements.activeFilter.textContent = active.length ? `현재 조건 · ${active.filter(Boolean).join(" · ")}` : data.meta.notice; } function renderResults() { updateUrl(); renderFilters(); document.querySelectorAll("[data-view]").forEach((button) => button.classList.toggle("active", button.dataset.view === state.view)); const renderers = { university: renderUniversityView, department: renderDepartmentView, admission: renderAdmissionView }; renderers[state.view](); } function sortedUniversities(includeKeyword = true) { return data.universities.filter((university) => universityMatches(university, includeKeyword)).sort((left, right) => left.priorityRank - right.priorityRank || left.name.localeCompare(right.name, "ko")); } function renderUniversityView() { const rows = sortedUniversities(true); elements.resultTitle.textContent = "대학별 보기"; elements.resultSummary.textContent = `${rows.length}개 대학 · 클릭하면 2028 전형 상세 확인`; elements.resultList.innerHTML = rows.slice(0, state.visible).map(renderUniversityCard).join(""); finishList(rows.length); } function renderUniversityCard(university) { const detailReady = university.detailReady && university.admissions.length > 0; const statusLabel = detailReady ? (university.structureStatus.includes("1차") ? "전형 1차 입력" : "전형 입력 완료") : "전형 입력 중"; const admissionLinks = university.departments.reduce((sum, department) => sum + Object.keys(department.quota || {}).length, 0); const detailText = detailReady ? `${university.admissions.length}개 전형 · ${university.departments.length ? `${university.departments.length}개 학과` : "대학 전형표"}` : "전형 상세 입력 중"; return `
`; } function renderDepartmentGrid(university, departments) { if (!departments.length) return `

입력 완료된 학과 중 검색 조건에 맞는 결과가 없습니다.

`; const admissionsById = Object.fromEntries(university.admissions.map((admission) => [admission.id, admission])); return `
${departments.map((department) => { const admissions = Object.keys(department.quota || {}).map((id) => admissionsById[id]).filter(Boolean); const total = Object.values(department.quota || {}).reduce((sum, value) => sum + (Number(value) || 0), 0); return `
${escapeHtml(department.college)} · ${escapeHtml(department.track)}

${escapeHtml(department.name)}

${total}명
${admissions.map((admission) => `${escapeHtml(admission.name)} ${department.quota[admission.id]}명`).join("")}
`; }).join("")}
`; } function departmentRows() { return sortedUniversities(false).flatMap((university) => university.departments.filter((department) => structuredDepartmentMatches(university, department)).map((department) => ({ university, department }))); } function renderDepartmentView() { const rows = departmentRows(); elements.resultTitle.textContent = "학과별 보기"; elements.resultSummary.textContent = `${rows.length.toLocaleString("ko-KR")}개 학과 · 상세 입력 완료 대학 기준`; elements.resultList.innerHTML = rows.slice(0, state.visible).map(({ university, department }) => { const total = Object.values(department.quota || {}).reduce((sum, value) => sum + (Number(value) || 0), 0); return `

${escapeHtml(university.name)} · ${escapeHtml(department.college)}

${escapeHtml(department.name)}

${escapeHtml(department.track)} · ${Object.keys(department.quota || {}).length}개 전형

2028 수시 선발인원 ${total}명
`; }).join(""); finishList(rows.length); } function admissionRows() { return sortedUniversities(false).flatMap((university) => { if (university.departments.length) { return university.departments.filter((department) => structuredDepartmentMatches(university, department)).flatMap((department) => Object.keys(department.quota || {}).map((admissionId) => ({ university, department, admission: university.admissions.find((row) => row.id === admissionId), quota: department.quota[admissionId] })).filter((row) => row.admission)); } return university.admissions.filter(admissionMatches).map((admission) => ({ university, department: null, admission, quota: admissionQuota(university, admission) })); }); } function admissionMatches(admission) { const terms = queryTerms(); if (!terms.length) return true; const text = [admission.name, admission.category, admission.selection, admission.ratio, admission.eligibility, admission.suneung, admission.naesin, admission.interview, admission.note].join(" ").toLowerCase(); return terms.every((term) => text.includes(term)); } function admissionQuota(university, admission) { if (Number.isFinite(Number(admission.quota))) return Number(admission.quota); return university.departments.reduce((sum, department) => sum + (Number(department.quota?.[admission.id]) || 0), 0); } function renderAdmissionView() { const rows = admissionRows(); elements.resultTitle.textContent = "전형별 보기"; elements.resultSummary.textContent = `${rows.length.toLocaleString("ko-KR")}개 학과·전형 연결`; elements.resultList.innerHTML = rows.slice(0, state.visible).map(({ university, department, admission, quota }) => `

${escapeHtml(university.name)}${department ? ` · ${escapeHtml(department.name)}` : ""}

${escapeHtml(admission.name)} · ${quota}명

${escapeHtml(admission.category)} · ${escapeHtml(admission.selection)}

${escapeHtml(admission.ratio)}
수능최저 ${escapeHtml(admission.suneung)}
`).join(""); finishList(rows.length); } function finishList(total) { elements.emptyState.hidden = total !== 0; elements.loadMore.hidden = state.visible >= total; } function showUniversityDetail(universityId) { const university = universityById.get(universityId); if (!university?.admissions?.length) return; elements.detailModalKicker.textContent = "2028 대학별 전형"; elements.detailModalTitle.textContent = `${university.name} 2028 전형`; elements.detailModalSubtitle.textContent = `${university.admissions.length}개 전형 · 전형방법·인원·수능최저·평가내용`; const introduction = university.detailSummary || (university.briefing ? `${university.briefing.difference} ${university.briefing.minimum}` : ""); const changes = university.changes || []; const changeSummary = changes.length ? `
2027 → 2028

2027 대비 변경사항

${changes.map((change) => `
${escapeHtml(change.category)}${escapeHtml(change.type)}${escapeHtml(change.scope)}

${escapeHtml(change.title)}

2027${escapeHtml(String(change.before || "").replace(/^2027:\s*/, ""))}

2028${escapeHtml(String(change.after || "").replace(/^2028:\s*/, ""))}

상담 포인트${escapeHtml(change.impact)}

`).join("")}
` : `

2027 대비 변경사항

${escapeHtml(university.changeReview || "2028 시행계획에 별도 변경 요약이 없거나 큰 변경을 확인하지 못했습니다.")}

`; elements.detailModalBody.innerHTML = `${changeSummary}${introduction ? `
2028 상담 한 줄

${escapeHtml(introduction)}

` : ""}
${university.admissions.map((admission, index) => { const quota = admissionQuota(university, admission); return `

${escapeHtml(admission.name)}

${escapeHtml(admission.category)}${escapeHtml(admission.selection)}
${quota ? `${quota.toLocaleString("ko-KR")}명` : "인원 확인 중"}
전형방법
${escapeHtml(admission.ratio)}
지원자격
${escapeHtml(admission.eligibility)}
수능최저
${escapeHtml(admission.suneung)}
학생부·평가
${escapeHtml(admission.naesin)}
면접
${escapeHtml(admission.interview)}
확인사항
${escapeHtml(admission.note)}
`; }).join("")}
`; openModal(elements.detailModal); } function showDepartmentDetail(universityId, departmentName) { const university = universityById.get(universityId); const department = university?.departments.find((row) => row.name === departmentName); if (!university || !department) return; const admissions = Object.keys(department.quota || {}).map((id) => university.admissions.find((row) => row.id === id)).filter(Boolean); elements.detailModalKicker.textContent = "2028 학과별 전형"; elements.detailModalTitle.textContent = `${university.name} · ${department.name}`; elements.detailModalSubtitle.textContent = `${department.college} · ${department.track} · 2028 시행계획 기준`; elements.detailModalBody.innerHTML = admissions.map((admission) => { const result = findResult(university, department, admission); return `

${escapeHtml(admission.name)} ${escapeHtml(admission.category)} ${department.quota[admission.id]}명

전형방법
${escapeHtml(admission.ratio)}
지원자격
${escapeHtml(admission.eligibility)}
수능최저
${escapeHtml(admission.suneung)}
학생부·평가
${escapeHtml(admission.naesin)}
면접
${escapeHtml(admission.interview)}
확인사항
${escapeHtml(admission.note)}
${renderResult(result)}
`; }).join(""); openModal(elements.detailModal); } function findResult(university, department, admission) { const universityKey = normalise(university.short || university.name).replace(/^국립/, "").replace(/대학교/g, "대"); const departmentKey = normalise(department.name); const admissionKey = normalise(admission.name); return (resultData.rows || []).filter((row) => normalise(row.university).replace(/^국립/, "").replace(/대학교/g, "대") === universityKey) .filter((row) => { const rowDepartment = normalise(row.department); return rowDepartment === departmentKey || rowDepartment.includes(departmentKey) || departmentKey.includes(rowDepartment); }).filter((row) => !row.category || row.category === admission.category) .map((row) => { const rowAdmission = normalise(row.admission); const keywords = (admission.resultKeywords || [admission.name]).map(normalise); const nameScore = keywords.some((keyword) => rowAdmission.includes(keyword) || keyword.includes(rowAdmission)) ? 100 : (rowAdmission.includes(admissionKey) || admissionKey.includes(rowAdmission) ? 70 : -1); return { row, score: nameScore + (numericGrade(row.grade9_2026) !== null ? 200 : 0) + (row.gradeBasis2026 ? 20 : 0) }; }).filter((item) => item.score >= 0).sort((left, right) => right.score - left.score)[0]?.row || null; } function renderResult(result) { if (!result) return `
2026 입결

학과·전형명이 정확히 연결되는 등급 자료가 없습니다.

`; const grade9 = numericGrade(result.grade9_2026); const grade5 = grade9 === null ? null : grade9To5Reference(grade9); return `
2026 · 9등급 원자료${grade9 === null ? "자료 없음" : grade9.toFixed(2)}${escapeHtml(result.gradeBasis2026 || "기준 미표기")}
2028 · 5등급 참고${grade5 === null ? "변환 불가" : `≈ ${grade5.toFixed(2)}`}공식 합격선 아님
`; } function numericGrade(value) { const match = String(value || "").match(/([1-9](?:\.\d+)?)/); const number = match ? Number(match[1]) : NaN; return Number.isFinite(number) && number >= 1 && number <= 9 ? number : null; } function grade9To5Reference(grade9) { if (grade9 <= FIVE_GRADE_REFERENCE_POINTS[0][1]) return 1; const last = FIVE_GRADE_REFERENCE_POINTS.at(-1); if (grade9 >= last[1]) return Math.min(5, last[0] + (grade9 - last[1]) / 2.125); for (let index = 0; index < FIVE_GRADE_REFERENCE_POINTS.length - 1; index += 1) { const left = FIVE_GRADE_REFERENCE_POINTS[index]; const right = FIVE_GRADE_REFERENCE_POINTS[index + 1]; if (left[1] <= grade9 && grade9 <= right[1]) return left[0] + ((grade9 - left[1]) / (right[1] - left[1])) * (right[0] - left[0]); } return null; } function openChanges() { renderChangeFilters(); renderChanges(); openModal(elements.changeModal); } function renderChangeFilters() { const categories = ["전체", ...new Set(data.changes.map((change) => change.category))]; elements.changeCategories.innerHTML = categories.map((category) => ``).join(""); } function renderChanges() { const query = state.changeQuery.trim().toLowerCase(); const rows = data.changes.filter((change) => state.changeCategory === "전체" || change.category === state.changeCategory).filter((change) => !query || Object.values(change).join(" ").toLowerCase().includes(query)); elements.changeList.innerHTML = rows.map((change) => `
${escapeHtml(change.university)}${escapeHtml(change.category)}${escapeHtml(change.type)}
${escapeHtml(change.scope)}

${escapeHtml(change.title)}

2027
${escapeHtml(String(change.before).replace(/^2027:\s*/, ""))}
2028
${escapeHtml(String(change.after).replace(/^2028:\s*/, ""))}

상담 포인트 ${escapeHtml(change.impact)}

`).join(""); } function openFocus(focus) { const rows = data.focusPrograms.rows.filter((row) => row.focus === focus); const groups = new Map(); rows.forEach((row) => { if (!groups.has(row.university)) groups.set(row.university, []); groups.get(row.university).push(row); }); elements.focusModalTitle.textContent = focus === "contract" ? "대기업 계약학과" : "핵심계열 상세"; elements.focusModalBody.innerHTML = [...groups.entries()].map(([university, universityRows]) => `

${escapeHtml(university)} · ${universityRows.reduce((sum, row) => sum + row.quota, 0)}명

${universityRows.map((row) => `
${escapeHtml(row.partner)}

${escapeHtml(row.department)} · ${row.quota}명

${escapeHtml(row.admission)} · ${escapeHtml(row.admissionCategory)}

${escapeHtml(row.method)}

수능최저 ${escapeHtml(row.minimum)}

`).join("")}
`).join(""); openModal(elements.focusModal); } function openModal(modal) { modal.classList.add("on"); modal.setAttribute("aria-hidden", "false"); document.body.classList.add("modal-open"); } function closeModal(modal) { modal.classList.remove("on"); modal.setAttribute("aria-hidden", "true"); if (!document.querySelector(".modal.on") && !elements.basket.classList.contains("on")) document.body.classList.remove("modal-open"); } function addCandidate(universityId, departmentName, admissionId = "") { const university = universityById.get(universityId); const department = departmentName ? university?.departments.find((row) => row.name === departmentName) : null; if (!university || (departmentName && !department)) return; const admissions = admissionId ? university.admissions.filter((row) => row.id === admissionId) : department ? Object.keys(department.quota || {}).map((id) => university.admissions.find((row) => row.id === id)).filter(Boolean) : university.admissions; admissions.forEach((admission) => { const departmentLabel = department?.name || "대학 전체"; const key = [university.id, departmentLabel, admission.id].join("|"); if (!basket.some((item) => item.key === key)) basket.push({ key, university: university.name, department: departmentLabel, admission: admission.name, quota: department ? department.quota[admission.id] : admissionQuota(university, admission), minimum: admission.suneung, method: admission.ratio }); }); saveStorage("gpt_susi_2028_basket", basket); renderBasket(); } function renderBasket() { elements.basketCount.textContent = basket.length; elements.basketStudent.textContent = [student.name, student.school, student.grade ? `5등급 내신 ${student.grade}` : "", student.interest].filter(Boolean).join(" · ") || "학생 정보를 입력하지 않았습니다."; elements.basketList.innerHTML = basket.length ? basket.map((item, index) => `
${escapeHtml(item.university)} · ${escapeHtml(item.department)}${escapeHtml(item.admission)} · ${item.quota}명수능최저 ${escapeHtml(item.minimum)}
`).join("") : `
학과 상세에서 상담 후보를 담아주세요.
`; } function toggleBasket(show) { elements.basket.classList.toggle("on", show); elements.basket.setAttribute("aria-hidden", String(!show)); elements.basketDim.hidden = !show; document.body.classList.toggle("modal-open", show); } function setMode(mode) { state.mode = mode; localStorage.setItem("gpt_susi_2028_mode", mode); document.querySelectorAll("[data-mode]").forEach((button) => button.classList.toggle("active", button.dataset.mode === mode)); const consulting = mode === "consult"; elements.consultStrip.hidden = !consulting; document.body.classList.toggle("consulting", consulting); } function saveStudent() { student = { name: elements.studentName.value.trim(), school: elements.studentSchool.value.trim(), grade: elements.studentGrade.value.trim(), interest: elements.studentInterest.value.trim() }; saveStorage("gpt_susi_2028_student", student); renderBasket(); } document.querySelectorAll("[data-mode]").forEach((button) => button.addEventListener("click", () => setMode(button.dataset.mode))); document.querySelectorAll(".tool-card[data-action]").forEach((card) => card.addEventListener("click", () => { if (card.dataset.action === "changes") openChanges(); if (card.dataset.action === "focus") openFocus(card.dataset.focus); if (card.dataset.action === "group") { state.group = card.dataset.group; state.visible = PAGE_SIZE; renderResults(); document.getElementById("searchArea").scrollIntoView({ behavior: "smooth" }); } })); document.getElementById("viewTabs").addEventListener("click", (event) => { const button = event.target.closest("[data-view]"); if (!button) return; state.view = button.dataset.view; state.visible = PAGE_SIZE; renderResults(); }); elements.universitySearch.addEventListener("input", (event) => { state.universityQuery = event.target.value; state.visible = PAGE_SIZE; renderResults(); }); elements.keywordSearch.addEventListener("input", (event) => { state.query = event.target.value; state.visible = PAGE_SIZE; renderResults(); }); document.getElementById("resetButton").addEventListener("click", () => { Object.assign(state, { universityQuery: "", query: "", group: "all", region: "전체", signal: "전체", visible: PAGE_SIZE }); elements.universitySearch.value = ""; elements.keywordSearch.value = ""; renderResults(); }); elements.priorityFilter.addEventListener("click", (event) => { const button = event.target.closest("[data-group]"); if (button) { state.group = button.dataset.group; state.visible = PAGE_SIZE; renderResults(); } }); elements.regionFilter.addEventListener("click", (event) => { const button = event.target.closest("[data-region]"); if (button) { state.region = button.dataset.region; state.visible = PAGE_SIZE; renderResults(); } }); elements.signalFilter.addEventListener("click", (event) => { const button = event.target.closest("[data-signal]"); if (button) { state.signal = button.dataset.signal; state.visible = PAGE_SIZE; renderResults(); } }); elements.loadMore.addEventListener("click", () => { state.visible += PAGE_SIZE; renderResults(); }); elements.resultList.addEventListener("click", (event) => { const universityDetail = event.target.closest("[data-university-detail]"); if (universityDetail) showUniversityDetail(universityDetail.dataset.universityDetail); const detail = event.target.closest("[data-detail-university]"); if (detail) showDepartmentDetail(detail.dataset.detailUniversity, detail.dataset.detailDepartment); const candidate = event.target.closest("[data-candidate-university]"); if (candidate) addCandidate(candidate.dataset.candidateUniversity, candidate.dataset.candidateDepartment, candidate.dataset.candidateAdmission || ""); }); elements.detailModalBody.addEventListener("click", (event) => { const candidate = event.target.closest("[data-candidate-university]"); if (candidate) addCandidate(candidate.dataset.candidateUniversity, candidate.dataset.candidateDepartment, candidate.dataset.candidateAdmission || ""); }); document.querySelectorAll("[data-close]").forEach((button) => button.addEventListener("click", () => closeModal(document.getElementById(button.dataset.close)))); document.querySelectorAll(".modal").forEach((modal) => modal.addEventListener("click", (event) => { if (event.target === modal) closeModal(modal); })); elements.changeSearch.addEventListener("input", (event) => { state.changeQuery = event.target.value; renderChanges(); }); elements.changeCategories.addEventListener("click", (event) => { const button = event.target.closest("[data-change-category]"); if (button) { state.changeCategory = button.dataset.changeCategory; renderChangeFilters(); renderChanges(); } }); [elements.studentName, elements.studentSchool, elements.studentGrade, elements.studentInterest].forEach((input) => input.addEventListener("input", saveStudent)); document.getElementById("basketOpen").addEventListener("click", () => toggleBasket(true)); document.getElementById("basketClose").addEventListener("click", () => toggleBasket(false)); elements.basketDim.addEventListener("click", () => toggleBasket(false)); elements.basketList.addEventListener("click", (event) => { const button = event.target.closest("[data-remove-candidate]"); if (button) { basket.splice(Number(button.dataset.removeCandidate), 1); saveStorage("gpt_susi_2028_basket", basket); renderBasket(); } }); document.getElementById("basketClear").addEventListener("click", () => { basket = []; saveStorage("gpt_susi_2028_basket", basket); renderBasket(); }); document.getElementById("basketCopy").addEventListener("click", async (event) => { const text = [`[2028 수시 상담]`, elements.basketStudent.textContent, ...basket.map((item, index) => `${index + 1}. ${item.university} ${item.department} / ${item.admission} ${item.quota}명 / 최저 ${item.minimum}`)].join("\n"); try { await navigator.clipboard.writeText(text); event.target.textContent = "복사 완료"; setTimeout(() => { event.target.textContent = "상담 메모 복사"; }, 1200); } catch (_error) { window.prompt("아래 내용을 복사하세요.", text); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { document.querySelectorAll(".modal.on").forEach(closeModal); toggleBasket(false); } }); elements.universitySearch.value = state.universityQuery; elements.keywordSearch.value = state.query; elements.studentName.value = student.name; elements.studentSchool.value = student.school; elements.studentGrade.value = student.grade; elements.studentInterest.value = student.interest; renderHero(); renderBasket(); setMode(state.mode); renderResults(); })();