ui 개편 완료

This commit is contained in:
2026-11-11 12:18:47 +09:00
parent 67a7fa0326
commit 15e8bcd37b
145 changed files with 141907 additions and 23029 deletions
+4
View File
@@ -25,3 +25,7 @@ pnpm-debug.log*
# OS Files # OS Files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Archives
*.zip
더세이브.zip
+2 -1
View File
@@ -104,6 +104,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -129,7 +130,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
return (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() return getShortSido(props.region?.sido || '')
}) })
const gradeShortName = computed(() => { const gradeShortName = computed(() => {
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div> <div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div> <div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div> <div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
+2 -2
View File
@@ -1020,7 +1020,7 @@
"sigungu": "영도구", "sigungu": "영도구",
"dong": "신선동3가", "dong": "신선동3가",
"address": "부산광역시 영도구 영상길 100", "address": "부산광역시 영도구 영상길 100",
"detailAddress": ", 부산영상예���고등학교 (신선동3가)", "detailAddress": ", 부산영상예술고등학교 (신선동3가)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -1510,7 +1510,7 @@
{ {
"code": "7150114", "code": "7150114",
"name": "신도고등학교", "name": "신도고등학교",
"region": "부산광��시", "region": "부산광역시",
"sigungu": "해운대구", "sigungu": "해운대구",
"dong": "좌동", "dong": "좌동",
"address": "부산광역시 해운대구 좌동순환로 64", "address": "부산광역시 해운대구 좌동순환로 64",
+4 -4
View File
@@ -1665,7 +1665,7 @@
"sigungu": "영도구", "sigungu": "영도구",
"dong": "신선동3가", "dong": "신선동3가",
"address": "부산광역시 영도구 영상길 100", "address": "부산광역시 영도구 영상길 100",
"detailAddress": ", 부산영상예���고등학교 (신선동3가)", "detailAddress": ", 부산영상예술고등학교 (신선동3가)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -1818,7 +1818,7 @@
{ {
"code": "7150114", "code": "7150114",
"name": "신도고등학교", "name": "신도고등학교",
"region": "부산광��시", "region": "부산광역시",
"sigungu": "해운대구", "sigungu": "해운대구",
"dong": "좌동", "dong": "좌동",
"address": "부산광역시 해운대구 좌동순환로 64", "address": "부산광역시 해운대구 좌동순환로 64",
@@ -2855,7 +2855,7 @@
"sigungu": "영도구", "sigungu": "영도구",
"dong": "신선동3가", "dong": "신선동3가",
"address": "부산광역시 영도구 영상길 100", "address": "부산광역시 영도구 영상길 100",
"detailAddress": ", 부산영상예���고등학교 (신선동3가)", "detailAddress": ", 부산영상예술고등학교 (신선동3가)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -3790,7 +3790,7 @@
{ {
"code": "7150114", "code": "7150114",
"name": "신도고등학교", "name": "신도고등학교",
"region": "부산광��시", "region": "부산광역시",
"sigungu": "해운대구", "sigungu": "해운대구",
"dong": "좌동", "dong": "좌동",
"address": "부산광역시 해운대구 좌동순환로 64", "address": "부산광역시 해운대구 좌동순환로 64",
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || 'ec58dcc43a4785d3358d9e2b603fea6bea4d89a3' }
] ]
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="flex items-center gap-1.5 text-slate-600 font-medium"> <div class="flex items-center gap-1.5 text-slate-600 font-medium">
<NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink> <NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<span>{{ parsed.dong }} 과외</span> <NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="hover:text-blue-600 transition-colors">{{ parsed.dong }} 과외</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong> <strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong>
</div> </div>
+3
View File
@@ -1,7 +1,10 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /admin
User-agent: Yeti User-agent: Yeti
Allow: / Allow: /
Disallow: /admin
Sitemap: https://soohyun.cloud/sitemap.xml Sitemap: https://soohyun.cloud/sitemap.xml
Sitemap: https://soohyun.cloud/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title><![CDATA[공부짝꿍]]></title> <title><![CDATA[공부짝꿍 - 부산 중등 수학 과외 · 부산 초·중·고 1:1 맞춤 과외]]></title>
<link>https://soohyun.cloud/</link> <link>https://soohyun.cloud/</link>
<description><![CDATA[부산 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description> <description><![CDATA[부산 중등 수학 과외, 부산 중등 과외, 부산 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 공부짝꿍]]></description>
<language>ko</language> <language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate> <lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://soohyun.cloud/rss.xml" rel="self" type="application/rss+xml"/> <atom:link href="https://soohyun.cloud/rss.xml" rel="self" type="application/rss+xml"/>
<item> <item>
<title><![CDATA[부산 초·중·고 1:1 맞춤 과외 매칭]]></title> <title><![CDATA[부산 중등 수학 과외 · 부산 중등 과외 | 초·중·고 1:1 맞춤 과외 공부짝꿍]]></title>
<link>https://soohyun.cloud/</link> <link>https://soohyun.cloud/</link>
<description><![CDATA[해운대·수영·동래 등 부산 전지역 과외 비용 비교 및 시범수업 신청]]></description> <description><![CDATA[부산 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/</guid> <guid>https://soohyun.cloud/</guid>
</item> </item>
<item> <item>
<title><![CDATA[우동 중학생 수학과외 1:1 맞춤 수업]]></title> <title><![CDATA[부산 초등 수학 과외 · 강동동 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%9A%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link> <link>https://soohyun.cloud/%EA%B0%95%EB%8F%99%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[해운대구 관내 학교 기출 분석]]></description> <description><![CDATA[부산 초등 수학 과외 및 강동동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%9A%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid> <guid>https://soohyun.cloud/%EA%B0%95%EB%8F%99%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item> </item>
<item> <item>
<title><![CDATA[사직동 고등학생 영어과외 1:1 맞춤 수업]]></title> <title><![CDATA[부산 중등 영어 과외 · 녹산동 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%82%AC%EC%A7%81%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link> <link>https://soohyun.cloud/%EB%85%B9%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[동래구 사직동 내신 서술형 집중 대비]]></description> <description><![CDATA[부산 중등 영어 과외 및 녹산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%82%AC%EC%A7%81%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid> <guid>https://soohyun.cloud/%EB%85%B9%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 고등 국어 과외 · 대저1동 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%8C%80%EC%A0%801%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[부산 고등 국어 과외 및 대저1동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%8C%80%EC%A0%801%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 초등 과학 과외 · 대저2동 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%8C%80%EC%A0%802%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[부산 초등 과학 과외 및 대저2동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%8C%80%EC%A0%802%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[부산 중등 사회 과외 · 명지동 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%AA%85%EC%A7%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[부산 중등 사회 과외 및 명지동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%AA%85%EC%A7%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[부산 고등 수학 과외 · 봉림동 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%B4%89%EB%A6%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[부산 고등 수학 과외 및 봉림동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%B4%89%EB%A6%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[부산 초등 영어 과외 · 성북동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%84%B1%EB%B6%81%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[부산 초등 영어 과외 및 성북동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%84%B1%EB%B6%81%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 중등 국어 과외 · 신호동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%8B%A0%ED%98%B8%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[부산 중등 국어 과외 및 신호동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%8B%A0%ED%98%B8%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 고등 과학 과외 · 구서동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EA%B5%AC%EC%84%9C%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[부산 고등 과학 과외 및 구서동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EA%B5%AC%EC%84%9C%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[부산 초등 사회 과외 · 금성동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EA%B8%88%EC%84%B1%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[부산 초등 사회 과외 및 금성동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EA%B8%88%EC%84%B1%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[부산 중등 수학 과외 · 남산동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%82%A8%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[부산 중등 수학 과외 및 남산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%82%A8%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[부산 고등 영어 과외 · 부곡동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EB%B6%80%EA%B3%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[부산 고등 영어 과외 및 부곡동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EB%B6%80%EA%B3%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 초등 국어 과외 · 서동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%84%9C%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[부산 초등 국어 과외 및 서동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%84%9C%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[부산 중등 과학 과외 · 장전동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EC%9E%A5%EC%A0%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[부산 중등 과학 과외 및 장전동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EC%9E%A5%EC%A0%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[부산 고등 사회 과외 · 기장읍 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.cloud/%EA%B8%B0%EC%9E%A5%EC%9D%8D/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[부산 고등 사회 과외 및 기장읍 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.cloud/%EA%B8%B0%EC%9E%A5%EC%9D%8D/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item> </item>
</channel> </channel>
</rss> </rss>
File diff suppressed because it is too large Load Diff
+1346 -1430
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://soohyun.cloud'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+5 -4
View File
@@ -4,6 +4,7 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://soohyun.cloud' const baseUrl = config.public.siteDomain || 'https://soohyun.cloud'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }> const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0] const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회'] const subjects = ['수학', '영어', '국어', '과학', '사회']
@@ -29,16 +30,16 @@ export default defineEventHandler((event) => {
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n` xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일) // 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
list.forEach(r => { uniqueDongs.forEach(dong => {
// 1. 동 대표 // 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합 // 2. 동/학년/과목 조합
grades.forEach(g => { grades.forEach(g => {
subjects.forEach(sub => { subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png` const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
}) })
}) })
}) })
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) { } else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
} }
tokens = tokens.map(t => t.trim()).filter(Boolean) tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생' let detectedGrade = '중학생'
let detectedGradeShort = '중등' let detectedGradeShort = '중등'
let detectedSubjectKey = 'english' let detectedSubjectKey = 'math'
let detectedSubjectName = '영어' let detectedSubjectName = '수학'
let detectedTerm = '과외' let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭 // 1. 토큰들 중 동 이름 매칭
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token) const reg = getRegionByDong(token)
if (reg) { if (reg) {
matchedRegion = reg matchedRegion = reg
matchedTokenIdx = i
break break
} }
} }
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) { if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) { if (found) {
matchedRegion = found matchedRegion = found
matchedTokenIdx = i
break break
} }
} }
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} }
} }
// 3. 학년 추출 // 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false let hasExplicitGrade = false
const fullText = tokens.join(' ') const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) { for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) { if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper' import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper' import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = { const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png', math: '/img/math-tutoring.png',
@@ -22,20 +22,25 @@ const CORE_GRADES = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '공부짝꿍', siteDomain = 'https://soohyun.cloud') { export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '공부짝꿍', siteDomain = 'https://soohyun.cloud') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = parsed.shortSido || sido.replace(/특별시|광역시|특별자치시|도/g, '').trim() const shortSido = parsed.shortSido || getShortSido(sido)
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등') const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong) const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png' const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
// 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀 // 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
const pageTitle = isRegionOnly const pageTitle = isRegionOnly
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외` ? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
// 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화) // 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화)
const pageDescription = isRegionOnly const pageDescription = isRegionOnly
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교를 확인하세요.` ? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교(${rotatedSubText})를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.` : `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외` const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외`
+2 -1
View File
@@ -104,6 +104,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -129,7 +130,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
return (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() return getShortSido(props.region?.sido || '')
}) })
const gradeShortName = computed(() => { const gradeShortName = computed(() => {
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div> <div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div> <div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div> <div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
+3 -3
View File
@@ -45,7 +45,7 @@
"sigungu": "북구", "sigungu": "북구",
"dong": "칠성동2가", "dong": "칠성동2가",
"address": "대구광역시 북구 칠성로17길 18", "address": "대구광역시 북구 칠성로17길 18",
"detailAddress": "(칠성동2가,경명여자중고등���교)", "detailAddress": "(칠성동2가,경명여자중고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
@@ -307,7 +307,7 @@
"address": "대구광역시 동구 팔공산로2길 16", "address": "대구광역시 동구 팔공산로2길 16",
"detailAddress": ", 달구벌고등학교 (덕곡동)", "detailAddress": ", 달구벌고등학교 (덕곡동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남���공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://dalgus.dge.hs.kr" "homepage": "http://dalgus.dge.hs.kr"
}, },
@@ -1114,7 +1114,7 @@
"detailAddress": ", 조일고등학교 (신평동)", "detailAddress": ", 조일고등학교 (신평동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "���립", "foundation": "사립",
"homepage": "https://choil.dge.hs.kr" "homepage": "https://choil.dge.hs.kr"
}, },
{ {
+6 -6
View File
@@ -81,7 +81,7 @@
"sigungu": "북구", "sigungu": "북구",
"dong": "칠성동2가", "dong": "칠성동2가",
"address": "대구광역시 북구 칠성로17길 18", "address": "대구광역시 북구 칠성로17길 18",
"detailAddress": "(칠성동2가,경명여자중고등���교)", "detailAddress": "(칠성동2가,경명여자중고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
@@ -637,7 +637,7 @@
"address": "대구광역시 동구 팔공산로2길 16", "address": "대구광역시 동구 팔공산로2길 16",
"detailAddress": ", 달구벌고등학교 (덕곡동)", "detailAddress": ", 달구벌고등학교 (덕곡동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남���공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://dalgus.dge.hs.kr" "homepage": "http://dalgus.dge.hs.kr"
} }
@@ -1302,7 +1302,7 @@
"detailAddress": ", 조일고등학교 (신평동)", "detailAddress": ", 조일고등학교 (신평동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "���립", "foundation": "사립",
"homepage": "https://choil.dge.hs.kr" "homepage": "https://choil.dge.hs.kr"
} }
], ],
@@ -1376,7 +1376,7 @@
"address": "대구광역시 동구 팔공산로2길 16", "address": "대구광역시 동구 팔공산로2길 16",
"detailAddress": ", 달구벌고등학교 (덕곡동)", "detailAddress": ", 달구벌고등학교 (덕곡동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남���공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://dalgus.dge.hs.kr" "homepage": "http://dalgus.dge.hs.kr"
}, },
@@ -1481,7 +1481,7 @@
"detailAddress": ", 조일고등학교 (신평동)", "detailAddress": ", 조일고등학교 (신평동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "���립", "foundation": "사립",
"homepage": "https://choil.dge.hs.kr" "homepage": "https://choil.dge.hs.kr"
}, },
{ {
@@ -1519,7 +1519,7 @@
"sigungu": "북구", "sigungu": "북구",
"dong": "칠성동2가", "dong": "칠성동2가",
"address": "대구광역시 북구 칠성로17길 18", "address": "대구광역시 북구 칠성로17길 18",
"detailAddress": "(칠성동2가,경명여자중고등���교)", "detailAddress": "(칠성동2가,경명여자중고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
+5 -4
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,10 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }, // 구글 서치 콘솔 사이트 소유확인 키 메타태그
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || 'dfb36e971115b72176e1c1e37c6ca27a95e855e3' },
// 구글 서치 콘솔 사이트 소유확인 키 메타태그
{ name: 'google-site-verification', content: 'DvXzTXFe_DBt8nrIiI7SwWYgOENNAWNW5op79syf5pM' } { name: 'google-site-verification', content: 'DvXzTXFe_DBt8nrIiI7SwWYgOENNAWNW5op79syf5pM' }
] ]
} }
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="flex items-center gap-1.5 text-slate-600 font-medium"> <div class="flex items-center gap-1.5 text-slate-600 font-medium">
<NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink> <NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<span>{{ parsed.dong }} 과외</span> <NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="hover:text-blue-600 transition-colors">{{ parsed.dong }} 과외</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong> <strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong>
</div> </div>
+3
View File
@@ -1,7 +1,10 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /admin
User-agent: Yeti User-agent: Yeti
Allow: / Allow: /
Disallow: /admin
Sitemap: https://soohyun.store/sitemap.xml Sitemap: https://soohyun.store/sitemap.xml
Sitemap: https://soohyun.store/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title><![CDATA[티처브릿지]]></title> <title><![CDATA[티처브릿지 - 대구 중등 수학 과외 · 대구 초·중·고 1:1 맞춤 과외]]></title>
<link>https://soohyun.store/</link> <link>https://soohyun.store/</link>
<description><![CDATA[대구 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description> <description><![CDATA[대구 중등 수학 과외, 대구 중등 과외, 대구 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 티처브릿지]]></description>
<language>ko</language> <language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate> <lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://soohyun.store/rss.xml" rel="self" type="application/rss+xml"/> <atom:link href="https://soohyun.store/rss.xml" rel="self" type="application/rss+xml"/>
<item> <item>
<title><![CDATA[대구 초·중·고 1:1 맞춤 과외 매칭]]></title> <title><![CDATA[대구 중등 수학 과외 · 대구 중등 과외 | 초·중·고 1:1 맞춤 과외 티처브릿지]]></title>
<link>https://soohyun.store/</link> <link>https://soohyun.store/</link>
<description><![CDATA[수성구 등 대구 전지역 과외 비용 비교 및 시범수업 신청]]></description> <description><![CDATA[대구 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/</guid> <guid>https://soohyun.store/</guid>
</item> </item>
<item> <item>
<title><![CDATA[범어동 중학생 수학과외 1:1 맞춤 수업]]></title> <title><![CDATA[대구 초등 수학 과외 · 군위읍 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%B2%94%EC%96%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link> <link>https://soohyun.store/%EA%B5%B0%EC%9C%84%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[수성구 학군 기출 족보 완파]]></description> <description><![CDATA[대구 초등 수학 과외 및 군위읍 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%B2%94%EC%96%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid> <guid>https://soohyun.store/%EA%B5%B0%EC%9C%84%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item> </item>
<item> <item>
<title><![CDATA[황금동 고등학생 영어과외 1:1 맞춤 수업]]></title> <title><![CDATA[대구 중등 영어 과외 · 효령면 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%ED%99%A9%EA%B8%88%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link> <link>https://soohyun.store/%ED%9A%A8%EB%A0%B9%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[수성구 황금동 내신 1등급 집중 코칭]]></description> <description><![CDATA[대구 중등 영어 과외 및 효령면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%ED%99%A9%EA%B8%88%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid> <guid>https://soohyun.store/%ED%9A%A8%EB%A0%B9%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 고등 국어 과외 · 대명동 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%8C%80%EB%AA%85%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[대구 고등 국어 과외 및 대명동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%8C%80%EB%AA%85%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 초등 과학 과외 · 봉덕동 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%B4%89%EB%8D%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[대구 초등 과학 과외 및 봉덕동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%B4%89%EB%8D%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[대구 중등 사회 과외 · 이천동 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EC%9D%B4%EC%B2%9C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[대구 중등 사회 과외 및 이천동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EC%9D%B4%EC%B2%9C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[대구 고등 수학 과외 · 감삼동 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EA%B0%90%EC%82%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[대구 고등 수학 과외 및 감삼동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EA%B0%90%EC%82%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[대구 초등 영어 과외 · 대곡동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%8C%80%EA%B3%A1%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[대구 초등 영어 과외 및 대곡동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%8C%80%EA%B3%A1%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 중등 국어 과외 · 도원동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%8F%84%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[대구 중등 국어 과외 및 도원동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%8F%84%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 고등 과학 과외 · 두류동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%91%90%EB%A5%98%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[대구 고등 과학 과외 및 두류동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%91%90%EB%A5%98%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[대구 초등 사회 과외 · 본동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%B3%B8%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[대구 초등 사회 과외 및 본동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%B3%B8%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[대구 중등 수학 과외 · 본리동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EB%B3%B8%EB%A6%AC%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[대구 중등 수학 과외 및 본리동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EB%B3%B8%EB%A6%AC%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[대구 고등 영어 과외 · 상인동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EC%83%81%EC%9D%B8%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[대구 고등 영어 과외 및 상인동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EC%83%81%EC%9D%B8%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 초등 국어 과외 · 성당동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EC%84%B1%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[대구 초등 국어 과외 및 성당동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EC%84%B1%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[대구 중등 과학 과외 · 송현동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EC%86%A1%ED%98%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[대구 중등 과학 과외 및 송현동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EC%86%A1%ED%98%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[대구 고등 사회 과외 · 신당동 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://soohyun.store/%EC%8B%A0%EB%8B%B9%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[대구 고등 사회 과외 및 신당동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://soohyun.store/%EC%8B%A0%EB%8B%B9%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item> </item>
</channel> </channel>
</rss> </rss>
File diff suppressed because it is too large Load Diff
+1186 -1260
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://soohyun.store'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+5 -4
View File
@@ -4,6 +4,7 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://soohyun.store' const baseUrl = config.public.siteDomain || 'https://soohyun.store'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }> const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0] const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회'] const subjects = ['수학', '영어', '국어', '과학', '사회']
@@ -29,16 +30,16 @@ export default defineEventHandler((event) => {
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n` xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일) // 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
list.forEach(r => { uniqueDongs.forEach(dong => {
// 1. 동 대표 // 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합 // 2. 동/학년/과목 조합
grades.forEach(g => { grades.forEach(g => {
subjects.forEach(sub => { subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png` const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
}) })
}) })
}) })
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) { } else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
} }
tokens = tokens.map(t => t.trim()).filter(Boolean) tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생' let detectedGrade = '중학생'
let detectedGradeShort = '중등' let detectedGradeShort = '중등'
let detectedSubjectKey = 'english' let detectedSubjectKey = 'math'
let detectedSubjectName = '영어' let detectedSubjectName = '수학'
let detectedTerm = '과외' let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭 // 1. 토큰들 중 동 이름 매칭
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token) const reg = getRegionByDong(token)
if (reg) { if (reg) {
matchedRegion = reg matchedRegion = reg
matchedTokenIdx = i
break break
} }
} }
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) { if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) { if (found) {
matchedRegion = found matchedRegion = found
matchedTokenIdx = i
break break
} }
} }
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} }
} }
// 3. 학년 추출 // 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false let hasExplicitGrade = false
const fullText = tokens.join(' ') const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) { for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) { if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper' import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper' import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = { const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png', math: '/img/math-tutoring.png',
@@ -22,20 +22,25 @@ const CORE_GRADES = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '티처브릿지', siteDomain = 'https://soohyun.store') { export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '티처브릿지', siteDomain = 'https://soohyun.store') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = parsed.shortSido || sido.replace(/특별시|광역시|특별자치시|도/g, '').trim() const shortSido = parsed.shortSido || getShortSido(sido)
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등') const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong) const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png' const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
// 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀 // 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
const pageTitle = isRegionOnly const pageTitle = isRegionOnly
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외` ? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
// 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화) // 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화)
const pageDescription = isRegionOnly const pageDescription = isRegionOnly
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교를 확인하세요.` ? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교(${rotatedSubText})를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.` : `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외` const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외`
+2 -1
View File
@@ -123,6 +123,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -148,7 +149,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
return (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() return getShortSido(props.region?.sido || '')
}) })
const gradeShortName = computed(() => { const gradeShortName = computed(() => {
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div> <div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div> <div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div> <div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
+143 -143
View File
@@ -2,10 +2,10 @@
{ {
"code": "7140327", "code": "7140327",
"name": "고려고등학교", "name": "고려고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "삼각동", "dong": "삼각동",
"address": "전남광주통합특별시 북구 설죽로 369", "address": "광주광역시 북구 설죽로 369",
"detailAddress": "(삼각동) 고려고등학교", "detailAddress": "(삼각동) 고려고등학교",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -15,10 +15,10 @@
{ {
"code": "7140328", "code": "7140328",
"name": "광덕고등학교", "name": "광덕고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "화정동", "dong": "화정동",
"address": "전남광주통합특별시 서구 화정로 202", "address": "광주광역시 서구 화정로 202",
"detailAddress": "(화정동)", "detailAddress": "(화정동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -28,10 +28,10 @@
{ {
"code": "7140329", "code": "7140329",
"name": "광일고등학교", "name": "광일고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "임곡동", "dong": "임곡동",
"address": "전남광주통합특별시 광산구 고봉로 812", "address": "광주광역시 광산구 고봉로 812",
"detailAddress": ", (임곡동, 광일고등학교) (임곡동)", "detailAddress": ", (임곡동, 광일고등학교) (임곡동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -41,10 +41,10 @@
{ {
"code": "7140330", "code": "7140330",
"name": "광주경신여자고등학교", "name": "광주경신여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "용봉동", "dong": "용봉동",
"address": "전남광주통합특별시 북구 금호로40번길 40", "address": "광주광역시 북구 금호로40번길 40",
"detailAddress": "(용봉동, 경신여고)", "detailAddress": "(용봉동, 경신여고)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -54,10 +54,10 @@
{ {
"code": "7140319", "code": "7140319",
"name": "광주고등학교", "name": "광주고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "동구", "sigungu": "동구",
"dong": "계림동", "dong": "계림동",
"address": "전남광주통합특별시 동구 중앙로 302", "address": "광주광역시 동구 중앙로 302",
"detailAddress": "(계림동)", "detailAddress": "(계림동)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -67,10 +67,10 @@
{ {
"code": "7140373", "code": "7140373",
"name": "광주공업고등학교", "name": "광주공업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "매곡동", "dong": "매곡동",
"address": "전남광주통합특별시 북구 설죽로315번길 14", "address": "광주광역시 북구 설죽로315번길 14",
"detailAddress": ", 광주공업고등학교 (매곡동)", "detailAddress": ", 광주공업고등학교 (매곡동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -80,10 +80,10 @@
{ {
"code": "7140320", "code": "7140320",
"name": "광주과학고등학교", "name": "광주과학고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "오룡동", "dong": "오룡동",
"address": "전남광주통합특별시 북구 첨단과기로 215", "address": "광주광역시 북구 첨단과기로 215",
"detailAddress": "(오룡동)", "detailAddress": "(오룡동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -93,10 +93,10 @@
{ {
"code": "7140331", "code": "7140331",
"name": "광주대동고등학교", "name": "광주대동고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "매월동", "dong": "매월동",
"address": "전남광주통합특별시 서구 전평길 50", "address": "광주광역시 서구 전평길 50",
"detailAddress": ", (매월동, 광주대동고등학교) ", "detailAddress": ", (매월동, 광주대동고등학교) ",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -106,10 +106,10 @@
{ {
"code": "7140368", "code": "7140368",
"name": "광주동성고등학교", "name": "광주동성고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "진월동", "dong": "진월동",
"address": "전남광주통합특별시 남구 서문대로627번길 20", "address": "광주광역시 남구 서문대로627번길 20",
"detailAddress": ", 광주동성고등학교 (진월동)", "detailAddress": ", 광주동성고등학교 (진월동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -119,10 +119,10 @@
{ {
"code": "7140332", "code": "7140332",
"name": "광주동신고등학교", "name": "광주동신고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "풍향동", "dong": "풍향동",
"address": "전남광주통합특별시 북구 동문대로 50", "address": "광주광역시 북구 동문대로 50",
"detailAddress": ", 광주동신고등학교 (풍향동)", "detailAddress": ", 광주동신고등학교 (풍향동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -132,10 +132,10 @@
{ {
"code": "7140333", "code": "7140333",
"name": "광주동신여자고등학교", "name": "광주동신여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "풍향동", "dong": "풍향동",
"address": "전남광주통합특별시 북구 동문대로 50", "address": "광주광역시 북구 동문대로 50",
"detailAddress": "(풍향동)", "detailAddress": "(풍향동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -145,10 +145,10 @@
{ {
"code": "7140334", "code": "7140334",
"name": "광주서석고등학교", "name": "광주서석고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "화정동", "dong": "화정동",
"address": "전남광주통합특별시 서구 화정로253번길 27", "address": "광주광역시 서구 화정로253번길 27",
"detailAddress": ", 광주서석고등학교 (화정동)", "detailAddress": ", 광주서석고등학교 (화정동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -158,10 +158,10 @@
{ {
"code": "7140335", "code": "7140335",
"name": "광주석산고등학교", "name": "광주석산고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "백운동", "dong": "백운동",
"address": "전남광주통합특별시 남구 백운로26번길 20", "address": "광주광역시 남구 백운로26번길 20",
"detailAddress": "(백운동,석산고등학교)", "detailAddress": "(백운동,석산고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -171,10 +171,10 @@
{ {
"code": "7140392", "code": "7140392",
"name": "광주소프트웨어마이스터고등학교", "name": "광주소프트웨어마이스터고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "송정동", "dong": "송정동",
"address": "전남광주통합특별시 광산구 상무대로 312", "address": "광주광역시 광산구 상무대로 312",
"detailAddress": "(송정동)", "detailAddress": "(송정동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -184,10 +184,10 @@
{ {
"code": "7140336", "code": "7140336",
"name": "광주수피아여자고등학교", "name": "광주수피아여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "양림동", "dong": "양림동",
"address": "전남광주통합특별시 남구 백서로 13", "address": "광주광역시 남구 백서로 13",
"detailAddress": ", (양림동, 광주수피아여고) ", "detailAddress": ", (양림동, 광주수피아여고) ",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -197,10 +197,10 @@
{ {
"code": "7140337", "code": "7140337",
"name": "광주숭일고등학교", "name": "광주숭일고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "일곡동", "dong": "일곡동",
"address": "전남광주통합특별시 북구 모룡대길 40", "address": "광주광역시 북구 모룡대길 40",
"detailAddress": ", 광주숭일고등학교 (일곡동)", "detailAddress": ", 광주숭일고등학교 (일곡동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -210,10 +210,10 @@
{ {
"code": "7140321", "code": "7140321",
"name": "광주여자고등학교", "name": "광주여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "화정동", "dong": "화정동",
"address": "전남광주통합특별시 서구 상무대로1014번길 43", "address": "광주광역시 서구 상무대로1014번길 43",
"detailAddress": ", 광주여자고등학교 (화정동)", "detailAddress": ", 광주여자고등학교 (화정동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -223,10 +223,10 @@
{ {
"code": "7140359", "code": "7140359",
"name": "광주여자상업고등학교", "name": "광주여자상업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "진월동", "dong": "진월동",
"address": "전남광주통합특별시 남구 서문대로627번길 20", "address": "광주광역시 남구 서문대로627번길 20",
"detailAddress": ", 광주여자상업고등학교 (진월동)", "detailAddress": ", 광주여자상업고등학교 (진월동)",
"type": "특성화고", "type": "특성화고",
"coedu": "여", "coedu": "여",
@@ -236,10 +236,10 @@
{ {
"code": "7140322", "code": "7140322",
"name": "광주예술고등학교", "name": "광주예술고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "매곡동", "dong": "매곡동",
"address": "전남광주통합특별시 북구 서하로 72", "address": "광주광역시 북구 서하로 72",
"detailAddress": "(매곡동)", "detailAddress": "(매곡동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -249,10 +249,10 @@
{ {
"code": "7140338", "code": "7140338",
"name": "광주인성고등학교", "name": "광주인성고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "송하동", "dong": "송하동",
"address": "전남광주통합특별시 남구 서문대로 390", "address": "광주광역시 남구 서문대로 390",
"detailAddress": ", 광주인성고등학교 (송하동 4-15)", "detailAddress": ", 광주인성고등학교 (송하동 4-15)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -262,10 +262,10 @@
{ {
"code": "7140396", "code": "7140396",
"name": "광주자동화설비마이스터고등학교", "name": "광주자동화설비마이스터고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "우산동", "dong": "우산동",
"address": "전남광주통합특별시 광산구 사암로92번길", "address": "광주광역시 광산구 사암로92번길",
"detailAddress": ", 136-26 (우산동)", "detailAddress": ", 136-26 (우산동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -275,10 +275,10 @@
{ {
"code": "7140369", "code": "7140369",
"name": "광주자연과학고등학교", "name": "광주자연과학고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "오치동", "dong": "오치동",
"address": "전남광주통합특별시 북구 능안로 30", "address": "광주광역시 북구 능안로 30",
"detailAddress": "(오치동,광주자연과학고등학교)", "detailAddress": "(오치동,광주자연과학고등학교)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -288,10 +288,10 @@
{ {
"code": "7140357", "code": "7140357",
"name": "광주전자공업고등학교", "name": "광주전자공업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "월계동", "dong": "월계동",
"address": "전남광주통합특별시 광산구 첨단중앙로 7", "address": "광주광역시 광산구 첨단중앙로 7",
"detailAddress": "(월계동, 광주전자공업고등학교)", "detailAddress": "(월계동, 광주전자공업고등학교)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -301,10 +301,10 @@
{ {
"code": "7140323", "code": "7140323",
"name": "광주제일고등학교", "name": "광주제일고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "누문동", "dong": "누문동",
"address": "전남광주통합특별시 북구 독립로237번길 33", "address": "광주광역시 북구 독립로237번길 33",
"detailAddress": ", 144 (누문동)", "detailAddress": ", 144 (누문동)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -314,10 +314,10 @@
{ {
"code": "7140339", "code": "7140339",
"name": "광주진흥고등학교", "name": "광주진흥고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "신창동", "dong": "신창동",
"address": "전남광주통합특별시 광산구 왕버들로 307", "address": "광주광역시 광산구 왕버들로 307",
"detailAddress": ", 광주진흥고등학교 (신창동)", "detailAddress": ", 광주진흥고등학교 (신창동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -327,10 +327,10 @@
{ {
"code": "7140324", "code": "7140324",
"name": "광주체육고등학교", "name": "광주체육고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "운암동", "dong": "운암동",
"address": "전남광주통합특별시 북구 하서로 55", "address": "광주광역시 북구 하서로 55",
"detailAddress": "(운암동184번지)", "detailAddress": "(운암동184번지)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -340,10 +340,10 @@
{ {
"code": "7140391", "code": "7140391",
"name": "광주푸른꿈창작학교", "name": "광주푸른꿈창작학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "주월동", "dong": "주월동",
"address": "전남광주통합특별시 남구 서문대로749번길 12", "address": "광주광역시 남구 서문대로749번길 12",
"detailAddress": "(주월동)", "detailAddress": "(주월동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -353,10 +353,10 @@
{ {
"code": "7140340", "code": "7140340",
"name": "국제고등학교", "name": "국제고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "삼각동", "dong": "삼각동",
"address": "전남광주통합특별시 북구 설죽로 433", "address": "광주광역시 북구 설죽로 433",
"detailAddress": "(삼각동)", "detailAddress": "(삼각동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -366,10 +366,10 @@
{ {
"code": "7140360", "code": "7140360",
"name": "금파공업고등학교", "name": "금파공업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "운암동", "dong": "운암동",
"address": "전남광주통합특별시 북구 금호로 80", "address": "광주광역시 북구 금호로 80",
"detailAddress": ", 금파공업고등학교 (운암동)", "detailAddress": ", 금파공업고등학교 (운암동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -379,10 +379,10 @@
{ {
"code": "7140341", "code": "7140341",
"name": "금호고등학교", "name": "금호고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "운암동", "dong": "운암동",
"address": "전남광주통합특별시 북구 금호로 100", "address": "광주광역시 북구 금호로 100",
"detailAddress": "(운암동, 금호고등학교)", "detailAddress": "(운암동, 금호고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -392,10 +392,10 @@
{ {
"code": "7140389", "code": "7140389",
"name": "금호중앙여자고등학교", "name": "금호중앙여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "운암동", "dong": "운암동",
"address": "전남광주통합특별시 북구 금호로 100", "address": "광주광역시 북구 금호로 100",
"detailAddress": "(운암동, 금호중앙여자고등학교)", "detailAddress": "(운암동, 금호중앙여자고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -405,10 +405,10 @@
{ {
"code": "7140342", "code": "7140342",
"name": "대광여자고등학교", "name": "대광여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "주월동", "dong": "주월동",
"address": "전남광주통합특별시 남구 서문대로 742번길 13", "address": "광주광역시 남구 서문대로 742번길 13",
"detailAddress": ", (주월동, 대광여고) ", "detailAddress": ", (주월동, 대광여고) ",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -418,10 +418,10 @@
{ {
"code": "7140343", "code": "7140343",
"name": "대성여자고등학교", "name": "대성여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "진월동", "dong": "진월동",
"address": "전남광주통합특별시 남구 서문대로663번길 37", "address": "광주광역시 남구 서문대로663번길 37",
"detailAddress": ", 대성여자고등학교 (진월동)", "detailAddress": ", 대성여자고등학교 (진월동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -431,10 +431,10 @@
{ {
"code": "7140382", "code": "7140382",
"name": "돈보스코학교", "name": "돈보스코학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "", "dong": "",
"address": "전남광주통합특별시 남구 서문대로749번마길 12", "address": "광주광역시 남구 서문대로749번마길 12",
"detailAddress": "돈보스코학교", "detailAddress": "돈보스코학교",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -444,10 +444,10 @@
{ {
"code": "7140370", "code": "7140370",
"name": "동명고등학교", "name": "동명고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "서봉동", "dong": "서봉동",
"address": "전남광주통합특별시 광산구 사이동길 16", "address": "광주광역시 광산구 사이동길 16",
"detailAddress": "(서봉동)", "detailAddress": "(서봉동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -457,10 +457,10 @@
{ {
"code": "7140344", "code": "7140344",
"name": "동아여자고등학교", "name": "동아여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "봉선동", "dong": "봉선동",
"address": "전남광주통합특별시 남구 오방로 16-10", "address": "광주광역시 남구 오방로 16-10",
"detailAddress": "(봉선동,동아여자고등학교)", "detailAddress": "(봉선동,동아여자고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -470,10 +470,10 @@
{ {
"code": "7140390", "code": "7140390",
"name": "동일미래과학고등학교", "name": "동일미래과학고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "방림동", "dong": "방림동",
"address": "전남광주통합특별시 남구 천변좌로 604", "address": "광주광역시 남구 천변좌로 604",
"detailAddress": ", 번길 12 (방림동)", "detailAddress": ", 번길 12 (방림동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -483,10 +483,10 @@
{ {
"code": "7140388", "code": "7140388",
"name": "명진고등학교", "name": "명진고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "수완동", "dong": "수완동",
"address": "전남광주통합특별시 광산구 수완로12번길 21", "address": "광주광역시 광산구 수완로12번길 21",
"detailAddress": ", 명진고등학교 (수완동)", "detailAddress": ", 명진고등학교 (수완동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -496,10 +496,10 @@
{ {
"code": "7140345", "code": "7140345",
"name": "문성고등학교", "name": "문성고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "봉선동", "dong": "봉선동",
"address": "전남광주통합특별시 남구 제석로 73", "address": "광주광역시 남구 제석로 73",
"detailAddress": ", (봉선동, 문성고등학교) ", "detailAddress": ", (봉선동, 문성고등학교) ",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -509,10 +509,10 @@
{ {
"code": "7140379", "code": "7140379",
"name": "문정여자고등학교", "name": "문정여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "오치동", "dong": "오치동",
"address": "전남광주통합특별시 북구 능안로30번길 33", "address": "광주광역시 북구 능안로30번길 33",
"detailAddress": ", 문정여자고등학교 (오치동, 5-12)", "detailAddress": ", 문정여자고등학교 (오치동, 5-12)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -522,10 +522,10 @@
{ {
"code": "7140375", "code": "7140375",
"name": "보문고등학교", "name": "보문고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "운수동", "dong": "운수동",
"address": "전남광주통합특별시 광산구 어등대로529번길 37", "address": "광주광역시 광산구 어등대로529번길 37",
"detailAddress": "(운수동)", "detailAddress": "(운수동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -535,10 +535,10 @@
{ {
"code": "7140394", "code": "7140394",
"name": "비아고등학교", "name": "비아고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "월계동", "dong": "월계동",
"address": "전남광주통합특별시 광산구 첨단중앙로181번길 42-26", "address": "광주광역시 광산구 첨단중앙로181번길 42-26",
"detailAddress": "(월계동, 비아고등학교)", "detailAddress": "(월계동, 비아고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -548,10 +548,10 @@
{ {
"code": "7140383", "code": "7140383",
"name": "빛고을고등학교", "name": "빛고을고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "신용동", "dong": "신용동",
"address": "전남광주통합특별시 북구 첨단연신로133번길 16", "address": "광주광역시 북구 첨단연신로133번길 16",
"detailAddress": "(신용동, 빛고을고등학교)", "detailAddress": "(신용동, 빛고을고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -561,10 +561,10 @@
{ {
"code": "7140346", "code": "7140346",
"name": "살레시오고등학교", "name": "살레시오고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "일곡동", "dong": "일곡동",
"address": "전남광주통합특별시 북구 모룡대길 67", "address": "광주광역시 북구 모룡대길 67",
"detailAddress": "(일곡동)", "detailAddress": "(일곡동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -574,10 +574,10 @@
{ {
"code": "7140347", "code": "7140347",
"name": "살레시오여자고등학교", "name": "살레시오여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "동구", "sigungu": "동구",
"dong": "지산동", "dong": "지산동",
"address": "전남광주통합특별시 동구 필문대로273번길 21", "address": "광주광역시 동구 필문대로273번길 21",
"detailAddress": ", (지산동, 살레시오여자고등학교) (지산동)", "detailAddress": ", (지산동, 살레시오여자고등학교) (지산동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -587,10 +587,10 @@
{ {
"code": "7140371", "code": "7140371",
"name": "상무고등학교", "name": "상무고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "쌍촌동", "dong": "쌍촌동",
"address": "전남광주통합특별시 서구 상무민주로32번길 28", "address": "광주광역시 서구 상무민주로32번길 28",
"detailAddress": "(쌍촌동)", "detailAddress": "(쌍촌동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -600,10 +600,10 @@
{ {
"code": "7140378", "code": "7140378",
"name": "상일여자고등학교", "name": "상일여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "쌍촌동", "dong": "쌍촌동",
"address": "전남광주통합특별시 서구 내방로 217", "address": "광주광역시 서구 내방로 217",
"detailAddress": "(쌍촌동)", "detailAddress": "(쌍촌동)",
"type": "자율고", "type": "자율고",
"coedu": "여", "coedu": "여",
@@ -613,10 +613,10 @@
{ {
"code": "7140384", "code": "7140384",
"name": "새날학교", "name": "새날학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "삼도동", "dong": "삼도동",
"address": "전남광주통합특별시 광산구 평동로 29", "address": "광주광역시 광산구 평동로 29",
"detailAddress": "(삼도동)", "detailAddress": "(삼도동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -626,10 +626,10 @@
{ {
"code": "7140348", "code": "7140348",
"name": "서강고등학교", "name": "서강고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "운암동", "dong": "운암동",
"address": "전남광주통합특별시 북구 서강로 1", "address": "광주광역시 북구 서강로 1",
"detailAddress": ", 서강고등학교 (운암동)", "detailAddress": ", 서강고등학교 (운암동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -639,10 +639,10 @@
{ {
"code": "7140376", "code": "7140376",
"name": "서진여자고등학교", "name": "서진여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "주월동", "dong": "주월동",
"address": "전남광주통합특별시 남구 서문대로742번길 20", "address": "광주광역시 남구 서문대로742번길 20",
"detailAddress": "(주월동)", "detailAddress": "(주월동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -652,10 +652,10 @@
{ {
"code": "7140352", "code": "7140352",
"name": "설월여자고등학교", "name": "설월여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "방림동", "dong": "방림동",
"address": "전남광주통합특별시 남구 천변좌로604번길 12", "address": "광주광역시 남구 천변좌로604번길 12",
"detailAddress": ", 설월여자고등학교 (방림동)", "detailAddress": ", 설월여자고등학교 (방림동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -665,10 +665,10 @@
{ {
"code": "7140386", "code": "7140386",
"name": "성덕고등학교", "name": "성덕고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "장덕동", "dong": "장덕동",
"address": "전남광주통합특별시 광산구 장덕로 75", "address": "광주광역시 광산구 장덕로 75",
"detailAddress": "(장덕동)", "detailAddress": "(장덕동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -678,10 +678,10 @@
{ {
"code": "7140349", "code": "7140349",
"name": "송원고등학교", "name": "송원고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "송하동", "dong": "송하동",
"address": "전남광주통합특별시 남구 송암로 73", "address": "광주광역시 남구 송암로 73",
"detailAddress": "(송하동, 송원고등학교)", "detailAddress": "(송하동, 송원고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -691,10 +691,10 @@
{ {
"code": "7140399", "code": "7140399",
"name": "송원미래인재고등학교", "name": "송원미래인재고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "송하동", "dong": "송하동",
"address": "전남광주통합특별시 남구 송암로 73", "address": "광주광역시 남구 송암로 73",
"detailAddress": ", 송원여자상업고등학교 (송하동)", "detailAddress": ", 송원여자상업고등학교 (송하동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -704,10 +704,10 @@
{ {
"code": "7140350", "code": "7140350",
"name": "송원여자고등학교", "name": "송원여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "송하동", "dong": "송하동",
"address": "전남광주통합특별시 남구 송암로 73", "address": "광주광역시 남구 송암로 73",
"detailAddress": ", (송하동, 송원여자고등학교) (송하동)", "detailAddress": ", (송하동, 송원여자고등학교) (송하동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -717,10 +717,10 @@
{ {
"code": "7140381", "code": "7140381",
"name": "수완고등학교", "name": "수완고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "수완동", "dong": "수완동",
"address": "전남광주통합특별시 광산구 장덕로 155", "address": "광주광역시 광산구 장덕로 155",
"detailAddress": ", 수완고등학교 (수완동)", "detailAddress": ", 수완고등학교 (수완동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -730,10 +730,10 @@
{ {
"code": "7140351", "code": "7140351",
"name": "숭덕고등학교", "name": "숭덕고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "월계동", "dong": "월계동",
"address": "전남광주통합특별시 광산구 월계로 117-8", "address": "광주광역시 광산구 월계로 117-8",
"detailAddress": "(월계동)", "detailAddress": "(월계동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -743,10 +743,10 @@
{ {
"code": "7140393", "code": "7140393",
"name": "숭의과학기술고등학교", "name": "숭의과학기술고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "방림동", "dong": "방림동",
"address": "전남광주통합특별시 남구 오방로 34-13", "address": "광주광역시 남구 오방로 34-13",
"detailAddress": "(방림동)", "detailAddress": "(방림동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -756,10 +756,10 @@
{ {
"code": "7140374", "code": "7140374",
"name": "운남고등학교", "name": "운남고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "운남동", "dong": "운남동",
"address": "전남광주통합특별시 광산구 임방울대로165번길 17", "address": "광주광역시 광산구 임방울대로165번길 17",
"detailAddress": ", 운남고등학교 (운남동)", "detailAddress": ", 운남고등학교 (운남동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -769,10 +769,10 @@
{ {
"code": "7140380", "code": "7140380",
"name": "장덕고등학교", "name": "장덕고등학교",
"region": "전남광주통합특���시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "장덕동", "dong": "장덕동",
"address": "전남광주통합특별시 광산구 수등로 45", "address": "광주광역시 광산구 수등로 45",
"detailAddress": "(장덕동)", "detailAddress": "(장덕동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -782,10 +782,10 @@
{ {
"code": "7140325", "code": "7140325",
"name": "전남고등학교", "name": "전남고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "치평동", "dong": "치평동",
"address": "전남광주통합특별시 서구 상무공원로 32", "address": "광주광역시 서구 상무공원로 32",
"detailAddress": "(치평동)", "detailAddress": "(치평동)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -795,10 +795,10 @@
{ {
"code": "7140358", "code": "7140358",
"name": "전남공업고등학교", "name": "전남공업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "신창동", "dong": "신창동",
"address": "전남광주통합특별시 광산구 왕버들로322번길 26", "address": "광주광역시 광산구 왕버들로322번길 26",
"detailAddress": "(신창동)", "detailAddress": "(신창동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -808,10 +808,10 @@
{ {
"code": "7380029", "code": "7380029",
"name": "전남대학교사범대학부설고등학교", "name": "전남대학교사범대학부설고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "용봉동", "dong": "용봉동",
"address": "전남광주통합특별시 북구 용봉로 45", "address": "광주광역시 북구 용봉로 45",
"detailAddress": "(용봉동)", "detailAddress": "(용봉동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -821,10 +821,10 @@
{ {
"code": "7140326", "code": "7140326",
"name": "전남여자고등학교", "name": "전남여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "동구", "sigungu": "동구",
"dong": "장동", "dong": "장동",
"address": "전남광주통합특별시 동구 제봉로158번길 8", "address": "광주광역시 동구 제봉로158번길 8",
"detailAddress": "(장동)", "detailAddress": "(장동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -834,10 +834,10 @@
{ {
"code": "7140361", "code": "7140361",
"name": "전남여자상업고등학교", "name": "전남여자상업고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "북구", "sigungu": "북구",
"dong": "삼각동", "dong": "삼각동",
"address": "전남광주통합특별시 북구 설죽로 433", "address": "광주광역시 북구 설죽로 433",
"detailAddress": ", (삼각동, 전남여자상업고등학교) ", "detailAddress": ", (삼각동, 전남여자상업고등학교) ",
"type": "특성화고", "type": "특성화고",
"coedu": "여", "coedu": "여",
@@ -847,10 +847,10 @@
{ {
"code": "7140353", "code": "7140353",
"name": "정광고등학교", "name": "정광고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "소촌동", "dong": "소촌동",
"address": "전남광주통합특별시 광산구 소촌로 90-36", "address": "광주광역시 광산구 소촌로 90-36",
"detailAddress": "(소촌동, 정광고등학교)", "detailAddress": "(소촌동, 정광고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -860,10 +860,10 @@
{ {
"code": "7140354", "code": "7140354",
"name": "조선대학교부속고등학교", "name": "조선대학교부속고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "동구", "sigungu": "동구",
"dong": "학동", "dong": "학동",
"address": "전남광주통합특별시 동구 의재로33번길 33", "address": "광주광역시 동구 의재로33번길 33",
"detailAddress": ", (학동, 조선대학교부속고등학교) (학동)", "detailAddress": ", (학동, 조선대학교부속고등학교) (학동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -873,10 +873,10 @@
{ {
"code": "7140355", "code": "7140355",
"name": "조선대학교여자고등학교", "name": "조선대학교여자고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "동구", "sigungu": "동구",
"dong": "지산동", "dong": "지산동",
"address": "전남광주통합특별시 동구 지산로 36", "address": "광주광역시 동구 지산로 36",
"detailAddress": ", 조선대학교여자고등학교 (지산동)", "detailAddress": ", 조선대학교여자고등학교 (지산동)",
"type": "일반고", "type": "일반고",
"coedu": "여", "coedu": "여",
@@ -886,10 +886,10 @@
{ {
"code": "7140377", "code": "7140377",
"name": "첨단고등학교", "name": "첨단고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "광산구", "sigungu": "광산구",
"dong": "월계동", "dong": "월계동",
"address": "전남광주통합특별시 광산구 산월로 12-17", "address": "광주광역시 광산구 산월로 12-17",
"detailAddress": "(월계동,첨단고등학교)", "detailAddress": "(월계동,첨단고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -899,11 +899,11 @@
{ {
"code": "7140372", "code": "7140372",
"name": "풍암고등학교", "name": "풍암고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "서구", "sigungu": "서구",
"dong": "풍암동", "dong": "풍암동",
"address": "전남광주통합특별시 서구 풍암신흥로39번길 20", "address": "광주광역시 서구 풍암신흥로39번길 20",
"detailAddress": ", 풍암고��학교 (풍암동)", "detailAddress": ", 풍암고등학교 (풍암동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -912,10 +912,10 @@
{ {
"code": "7140356", "code": "7140356",
"name": "호남삼육고등학교", "name": "호남삼육고등학교",
"region": "전남광주통합특별시(광주)", "region": "광주광역시",
"sigungu": "남구", "sigungu": "남구",
"dong": "주월동", "dong": "주월동",
"address": "전남광주통합특별시 남구 회서로 30", "address": "광주광역시 남구 회서로 30",
"detailAddress": "(주월동,삼육중고등학교)", "detailAddress": "(주월동,삼육중고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || '48cf582068341a6e4bebde6900b3e0a0e198f4b6' }
] ]
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="flex items-center gap-1.5 text-slate-600 font-medium"> <div class="flex items-center gap-1.5 text-slate-600 font-medium">
<NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink> <NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<span>{{ parsed.dong }} 과외</span> <NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="hover:text-blue-600 transition-colors">{{ parsed.dong }} 과외</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-400">›</span>
<strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong> <strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong>
</div> </div>
+3
View File
@@ -1,7 +1,10 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /admin
User-agent: Yeti User-agent: Yeti
Allow: / Allow: /
Disallow: /admin
Sitemap: https://aeseo.store/sitemap.xml Sitemap: https://aeseo.store/sitemap.xml
Sitemap: https://aeseo.store/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title><![CDATA[클래스픽]]></title> <title><![CDATA[클래스픽 - 광주 중등 수학 과외 · 광주 초·중·고 1:1 맞춤 과외]]></title>
<link>https://aeseo.store/</link> <link>https://aeseo.store/</link>
<description><![CDATA[광주 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description> <description><![CDATA[광주 중등 수학 과외, 광주 중등 과외, 광주 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 클래스픽]]></description>
<language>ko</language> <language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate> <lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://aeseo.store/rss.xml" rel="self" type="application/rss+xml"/> <atom:link href="https://aeseo.store/rss.xml" rel="self" type="application/rss+xml"/>
<item> <item>
<title><![CDATA[광주 초·중·고 1:1 맞춤 과외 매칭]]></title> <title><![CDATA[광주 중등 수학 과외 · 광주 중등 과외 | 초·중·고 1:1 맞춤 과외 클래스픽]]></title>
<link>https://aeseo.store/</link> <link>https://aeseo.store/</link>
<description><![CDATA[봉선·수완 등 광주 전지역 과외 비용 비교 및 시범수업 신청]]></description> <description><![CDATA[광주 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/</guid> <guid>https://aeseo.store/</guid>
</item> </item>
<item> <item>
<title><![CDATA[봉선동 중학생 수학과외 1:1 맞춤 수업]]></title> <title><![CDATA[광주 초등 수학 과외 · 도산동 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EB%B4%89%EC%84%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link> <link>https://aeseo.store/%EB%8F%84%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[봉선동 학군 내신 1등급 솔루션]]></description> <description><![CDATA[광주 초등 수학 과외 및 도산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EB%B4%89%EC%84%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid> <guid>https://aeseo.store/%EB%8F%84%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item> </item>
<item> <item>
<title><![CDATA[수완동 고등학생 영어과외 1:1 맞춤 수업]]></title> <title><![CDATA[광주 중등 영어 과외 · 비아동 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%88%98%EC%99%84%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link> <link>https://aeseo.store/%EB%B9%84%EC%95%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[광산구 수완동 내신 서술형 완벽 대비]]></description> <description><![CDATA[광주 중등 영어 과외 및 비아동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%88%98%EC%99%84%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid> <guid>https://aeseo.store/%EB%B9%84%EC%95%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 고등 국어 과외 · 삼도동 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%82%BC%EB%8F%84%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[광주 고등 국어 과외 및 삼도동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%82%BC%EB%8F%84%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 초등 과학 과외 · 서봉동 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%84%9C%EB%B4%89%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[광주 초등 과학 과외 및 서봉동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%84%9C%EB%B4%89%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[광주 중등 사회 과외 · 소촌동 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%86%8C%EC%B4%8C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[광주 중등 사회 과외 및 소촌동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%86%8C%EC%B4%8C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[광주 고등 수학 과외 · 송정동 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%86%A1%EC%A0%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[광주 고등 수학 과외 및 송정동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%86%A1%EC%A0%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[광주 초등 영어 과외 · 수완동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%88%98%EC%99%84%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[광주 초등 영어 과외 및 수완동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%88%98%EC%99%84%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 중등 국어 과외 · 신가동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%8B%A0%EA%B0%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[광주 중등 국어 과외 및 신가동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%8B%A0%EA%B0%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 고등 과학 과외 · 신창동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%8B%A0%EC%B0%BD%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[광주 고등 과학 과외 및 신창동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%8B%A0%EC%B0%BD%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[광주 초등 사회 과외 · 우산동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9A%B0%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[광주 초등 사회 과외 및 우산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9A%B0%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[광주 중등 수학 과외 · 운남동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9A%B4%EB%82%A8%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[광주 중등 수학 과외 및 운남동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9A%B4%EB%82%A8%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[광주 고등 영어 과외 · 운수동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9A%B4%EC%88%98%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[광주 고등 영어 과외 및 운수동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9A%B4%EC%88%98%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 초등 국어 과외 · 월계동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9B%94%EA%B3%84%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[광주 초등 국어 과외 및 월계동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9B%94%EA%B3%84%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[광주 중등 과학 과외 · 임곡동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9E%84%EA%B3%A1%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[광주 중등 과학 과외 및 임곡동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9E%84%EA%B3%A1%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[광주 고등 사회 과외 · 장덕동 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.store/%EC%9E%A5%EB%8D%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[광주 고등 사회 과외 및 장덕동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.store/%EC%9E%A5%EB%8D%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item> </item>
</channel> </channel>
</rss> </rss>
File diff suppressed because it is too large Load Diff
+850 -1016
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.store'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+5 -4
View File
@@ -4,6 +4,7 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.store' const baseUrl = config.public.siteDomain || 'https://aeseo.store'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }> const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0] const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회'] const subjects = ['수학', '영어', '국어', '과학', '사회']
@@ -29,16 +30,16 @@ export default defineEventHandler((event) => {
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n` xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일) // 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
list.forEach(r => { uniqueDongs.forEach(dong => {
// 1. 동 대표 // 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합 // 2. 동/학년/과목 조합
grades.forEach(g => { grades.forEach(g => {
subjects.forEach(sub => { subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png` const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
}) })
}) })
}) })
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) { } else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
} }
tokens = tokens.map(t => t.trim()).filter(Boolean) tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생' let detectedGrade = '중학생'
let detectedGradeShort = '중등' let detectedGradeShort = '중등'
let detectedSubjectKey = 'english' let detectedSubjectKey = 'math'
let detectedSubjectName = '영어' let detectedSubjectName = '수학'
let detectedTerm = '과외' let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭 // 1. 토큰들 중 동 이름 매칭
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token) const reg = getRegionByDong(token)
if (reg) { if (reg) {
matchedRegion = reg matchedRegion = reg
matchedTokenIdx = i
break break
} }
} }
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) { if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) { if (found) {
matchedRegion = found matchedRegion = found
matchedTokenIdx = i
break break
} }
} }
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} }
} }
// 3. 학년 추출 // 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false let hasExplicitGrade = false
const fullText = tokens.join(' ') const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) { for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) { if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper' import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper' import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = { const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png', math: '/img/math-tutoring.png',
@@ -22,20 +22,25 @@ const CORE_GRADES = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '클래스픽', siteDomain = 'https://aeseo.store') { export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '클래스픽', siteDomain = 'https://aeseo.store') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = parsed.shortSido || sido.replace(/특별시|광역시|특별자치시|도/g, '').trim() const shortSido = parsed.shortSido || getShortSido(sido)
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등') const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong) const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png' const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
// 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀 // 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
const pageTitle = isRegionOnly const pageTitle = isRegionOnly
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외` ? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
// 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화) // 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화)
const pageDescription = isRegionOnly const pageDescription = isRegionOnly
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교를 확인하세요.` ? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교(${rotatedSubText})를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.` : `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외` const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외`
+2 -1
View File
@@ -101,6 +101,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -126,7 +127,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
const s = (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() const s = getShortSido(props.region?.sido || '')
return s || '경기' return s || '경기'
}) })
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div> <div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div> <div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div> <div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
+9 -9
View File
@@ -552,7 +552,7 @@
"sigungu": "안산시 단원구", "sigungu": "안산시 단원구",
"dong": "고잔동", "dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222", "address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)", "detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -1139,7 +1139,7 @@
"address": "경기도 하남시 신평로 25", "address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)", "detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고", "type": "일반고",
"coedu": "��여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do" "homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
}, },
@@ -1244,7 +1244,7 @@
"detailAddress": "(대부북동)", "detailAddress": "(대부북동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "��립", "foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do" "homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
}, },
{ {
@@ -1604,7 +1604,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "화성시", "sigungu": "화성시",
"dong": "반송동", "dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27", "address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)", "detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -2555,7 +2555,7 @@
"dong": "본오동", "dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34", "address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)", "detailAddress": "(본오동, 상록고등학교)",
"type": "���반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr" "homepage": "https://sangrok-h.goeas.kr"
@@ -2849,7 +2849,7 @@
{ {
"code": "7530475", "code": "7530475",
"name": "성안고등학교", "name": "성안고등학교",
"region": "경기��", "region": "경기도",
"sigungu": "안산시 상록구", "sigungu": "안산시 상록구",
"dong": "사동", "dong": "사동",
"address": "경기도 안산시 상록구 용신로 170", "address": "경기도 안산시 상록구 용신로 170",
@@ -2998,7 +2998,7 @@
"address": "경기도 시흥시 수인로3287번길 26", "address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)", "detailAddress": "(은행동,소래고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "���여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do" "homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
}, },
@@ -3387,7 +3387,7 @@
"dong": "범박동", "dong": "범박동",
"address": "경기도 부천시 은성로 172-20", "address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)", "detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do" "homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -6063,7 +6063,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "평택시", "sigungu": "평택시",
"dong": "고덕면", "dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185", "address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)", "detailAddress": "(고덕면)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
+18 -18
View File
@@ -799,7 +799,7 @@
"sigungu": "안산시 단원구", "sigungu": "안산시 단원구",
"dong": "고잔동", "dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222", "address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)", "detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -2093,7 +2093,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "화성시", "sigungu": "화성시",
"dong": "반송동", "dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27", "address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)", "detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -2236,7 +2236,7 @@
"address": "경기도 하남시 신평로 25", "address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)", "detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고", "type": "일반고",
"coedu": "��여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do" "homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
}, },
@@ -2353,7 +2353,7 @@
"detailAddress": "(대부북동)", "detailAddress": "(대부북동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "��립", "foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do" "homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
} }
], ],
@@ -3570,7 +3570,7 @@
"dong": "범박동", "dong": "범박동",
"address": "경기도 부천시 은성로 172-20", "address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)", "detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do" "homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -4089,7 +4089,7 @@
"dong": "본오동", "dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34", "address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)", "detailAddress": "(본오동, 상록고등학교)",
"type": "���반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr" "homepage": "https://sangrok-h.goeas.kr"
@@ -4329,7 +4329,7 @@
{ {
"code": "7530475", "code": "7530475",
"name": "성안고등학교", "name": "성안고등학교",
"region": "경기��", "region": "경기도",
"sigungu": "안산시 상록구", "sigungu": "안산시 상록구",
"dong": "사동", "dong": "사동",
"address": "경기도 안산시 상록구 용신로 170", "address": "경기도 안산시 상록구 용신로 170",
@@ -4479,7 +4479,7 @@
"address": "경기도 시흥시 수인로3287번길 26", "address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)", "detailAddress": "(은행동,소래고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "���여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do" "homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
}, },
@@ -6717,7 +6717,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "평택시", "sigungu": "평택시",
"dong": "고덕면", "dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185", "address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)", "detailAddress": "(고덕면)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -7685,7 +7685,7 @@
"address": "경기도 하남시 신평로 25", "address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)", "detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고", "type": "일반고",
"coedu": "��여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do" "homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
}, },
@@ -8052,7 +8052,7 @@
"dong": "범박동", "dong": "범박동",
"address": "경기도 부천시 은성로 172-20", "address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)", "detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do" "homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -8567,7 +8567,7 @@
"dong": "본오동", "dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34", "address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)", "detailAddress": "(본오동, 상록고등학교)",
"type": "���반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr" "homepage": "https://sangrok-h.goeas.kr"
@@ -8575,7 +8575,7 @@
{ {
"code": "7530475", "code": "7530475",
"name": "성안고등학교", "name": "성안고등학교",
"region": "경기��", "region": "경기도",
"sigungu": "안산시 상록구", "sigungu": "안산시 상록구",
"dong": "사동", "dong": "사동",
"address": "경기도 안산시 상록구 용신로 170", "address": "경기도 안산시 상록구 용신로 170",
@@ -8931,7 +8931,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "평택시", "sigungu": "평택시",
"dong": "고덕면", "dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185", "address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)", "detailAddress": "(고덕면)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -9276,7 +9276,7 @@
"address": "경기도 시흥시 수인로3287번길 26", "address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)", "detailAddress": "(은행동,소래고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "���여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do" "homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
}, },
@@ -10001,7 +10001,7 @@
"sigungu": "안산시 단원구", "sigungu": "안산시 단원구",
"dong": "고잔동", "dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222", "address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)", "detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
@@ -10030,7 +10030,7 @@
"detailAddress": "(대부북동)", "detailAddress": "(대부북동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "��립", "foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do" "homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
}, },
{ {
@@ -12303,7 +12303,7 @@
"region": "경기도", "region": "경기도",
"sigungu": "화성시", "sigungu": "화성시",
"dong": "반송동", "dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27", "address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)", "detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고", "type": "특목고",
"coedu": "남여공학", "coedu": "남여공학",
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || 'bfbfcb6ed8c107d05503feed914769a29b07cf06' }
] ]
} }
} }
+4 -1
View File
@@ -1,7 +1,10 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /admin
User-agent: Yeti User-agent: Yeti
Allow: / Allow: /
Disallow: /admin
Sitemap: https://aeseo.shop/sitemap.xml Sitemap: https://aeseo.xyz/sitemap.xml
Sitemap: https://aeseo.xyz/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title><![CDATA[새솔 1:1 맞춤과외]]></title> <title><![CDATA[새솔 1:1 맞춤과외 - 경기 중등 수학 과외 · 경기 초·중·고 1:1 맞춤 과외]]></title>
<link>https://aeseo.shop/</link> <link>https://aeseo.shop/</link>
<description><![CDATA[경기 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description> <description><![CDATA[경기 중등 수학 과외, 경기 중등 과외, 경기 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 새솔 1:1 맞춤과외]]></description>
<language>ko</language> <language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate> <lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://aeseo.shop/rss.xml" rel="self" type="application/rss+xml"/> <atom:link href="https://aeseo.shop/rss.xml" rel="self" type="application/rss+xml"/>
<item> <item>
<title><![CDATA[경기 초·중·고 1:1 맞춤 과외 매칭]]></title> <title><![CDATA[경기 중등 수학 과외 · 경기 중등 과외 | 초·중·고 1:1 맞춤 과외 새솔 1:1 맞춤과외]]></title>
<link>https://aeseo.shop/</link> <link>https://aeseo.shop/</link>
<description><![CDATA[과외 비용·시세 투명 비교부터 무료 시범수업 신청까지]]></description> <description><![CDATA[경기 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/</guid> <guid>https://aeseo.shop/</guid>
</item> </item>
<item> <item>
<title><![CDATA[수내동 중학생 수학과외 1:1 맞춤 수업]]></title> <title><![CDATA[경기 초등 수학 과외 · 가평읍 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%88%98%EB%82%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link> <link>https://aeseo.shop/%EA%B0%80%ED%8F%89%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[분당 수내동 인근 학교별 내신 기출 분석]]></description> <description><![CDATA[경기 초등 수학 과외 및 가평읍 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%88%98%EB%82%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid> <guid>https://aeseo.shop/%EA%B0%80%ED%8F%89%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item> </item>
<item> <item>
<title><![CDATA[마두동 고등학생 영어과외 1:1 맞춤 수업]]></title> <title><![CDATA[경기 중등 영어 과외 · 설악면 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%A7%88%EB%91%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link> <link>https://aeseo.shop/%EC%84%A4%EC%95%85%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[일산 학군 내신 서술형 대비]]></description> <description><![CDATA[경기 중등 영어 과외 및 설악면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EB%A7%88%EB%91%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid> <guid>https://aeseo.shop/%EC%84%A4%EC%95%85%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 고등 국어 과외 · 조종면 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%A1%B0%EC%A2%85%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 고등 국어 과외 및 조종면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%A1%B0%EC%A2%85%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 초등 과학 과외 · 청평면 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%B2%AD%ED%8F%89%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 초등 과학 과외 및 청평면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%B2%AD%ED%8F%89%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 중등 사회 과외 · 고양동 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EA%B3%A0%EC%96%91%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 중등 사회 과외 및 고양동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EA%B3%A0%EC%96%91%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[경기 고등 수학 과외 · 관산동 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EA%B4%80%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[경기 고등 수학 과외 및 관산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EA%B4%80%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 초등 영어 과외 · 도내동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%8F%84%EB%82%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[경기 초등 영어 과외 및 도내동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EB%8F%84%EB%82%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 중등 국어 과외 · 동산동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%8F%99%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 중등 국어 과외 및 동산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EB%8F%99%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 고등 과학 과외 · 삼송동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%82%BC%EC%86%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 고등 과학 과외 및 삼송동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%82%BC%EC%86%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 초등 사회 과외 · 성사동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%84%B1%EC%82%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 초등 사회 과외 및 성사동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%84%B1%EC%82%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[경기 중등 수학 과외 · 신원동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%8B%A0%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[경기 중등 수학 과외 및 신원동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%8B%A0%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 고등 영어 과외 · 주교동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%A3%BC%EA%B5%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[경기 고등 영어 과외 및 주교동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%A3%BC%EA%B5%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 초등 국어 과외 · 토당동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%86%A0%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 초등 국어 과외 및 토당동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%86%A0%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 중등 과학 과외 · 행신동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%96%89%EC%8B%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 중등 과학 과외 및 행신동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%96%89%EC%8B%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 고등 사회 과외 · 향동동 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%96%A5%EB%8F%99%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 고등 사회 과외 및 향동동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%96%A5%EB%8F%99%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item> </item>
</channel> </channel>
</rss> </rss>
File diff suppressed because it is too large Load Diff
+5730 -7556
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.shop'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+16 -29
View File
@@ -4,8 +4,11 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.shop' const baseUrl = config.public.siteDomain || 'https://aeseo.shop'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }> const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0] const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = { const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`, '수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`, '영어': `${baseUrl}/img/english-tutoring.png`,
@@ -13,46 +16,30 @@ export default defineEventHandler((event) => {
'과학': `${baseUrl}/img/science-tutoring.png`, '과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png` '사회': `${baseUrl}/img/social-studies-tutoring.png`
} }
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n` let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n` xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인 (대표 이미지 + 5대 과목 이미지 모두 포함) // 메인
xml += ` <url>\n` xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <loc>${baseUrl}/</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>daily</changefreq>\n`
xml += ` <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
subjects.forEach(sub => { xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
}) xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n` xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성 // 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
list.forEach(r => { uniqueDongs.forEach(dong => {
// 1. 동 대표 // 1. 동 대표
xml += ` <url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.8</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 2. 동/학년/과목 대표 조합 // 2. 동/학년/과목 조합
grades.forEach(g => { grades.forEach(g => {
subjects.forEach(sub => { subjects.forEach(sub => {
xml += ` <url>\n` const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.7</priority>\n`
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n`
xml += ` </url>\n`
}) })
}) })
}) })
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) { } else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
} }
tokens = tokens.map(t => t.trim()).filter(Boolean) tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생' let detectedGrade = '중학생'
let detectedGradeShort = '중등' let detectedGradeShort = '중등'
let detectedSubjectKey = 'english' let detectedSubjectKey = 'math'
let detectedSubjectName = '영어' let detectedSubjectName = '수학'
let detectedTerm = '과외' let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭 // 1. 토큰들 중 동 이름 매칭
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token) const reg = getRegionByDong(token)
if (reg) { if (reg) {
matchedRegion = reg matchedRegion = reg
matchedTokenIdx = i
break break
} }
} }
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) { if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) { if (found) {
matchedRegion = found matchedRegion = found
matchedTokenIdx = i
break break
} }
} }
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} }
} }
// 3. 학년 추출 // 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false let hasExplicitGrade = false
const fullText = tokens.join(' ') const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) { for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) { if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper' import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper' import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = { const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png', math: '/img/math-tutoring.png',
@@ -22,18 +22,23 @@ const GRADE_LIST = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '새솔 1:1 맞춤과외', siteDomain = 'https://aeseo.shop') { export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '새솔 1:1 맞춤과외', siteDomain = 'https://aeseo.shop') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = (sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() || '경기' const shortSido = getShortSido(sido) || '경기'
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등') const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong) const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png' const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
const pageTitle = isRegionOnly const pageTitle = isRegionOnly
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외` ? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
const pageDescription = isRegionOnly const pageDescription = isRegionOnly
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교를 확인하세요.` ? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교(${rotatedSubText})를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.` : `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}` const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}`
const canonicalUrl = isRegionOnly const canonicalUrl = isRegionOnly
+2 -1
View File
@@ -101,6 +101,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -126,7 +127,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
const s = (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() const s = getShortSido(props.region?.sido || '')
return s || '인천' return s || '인천'
}) })
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div> <div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div> <div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div> <div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
+4 -4
View File
@@ -821,7 +821,7 @@
{ {
"code": "7310449", "code": "7310449",
"name": "인천과학예술영재학교", "name": "인천과학예술영재학교",
"region": "인천광��시", "region": "인천광역시",
"sigungu": "연수구", "sigungu": "연수구",
"dong": "송도동", "dong": "송도동",
"address": "인천광역시 연수구 아카데미로 192", "address": "인천광역시 연수구 아카데미로 192",
@@ -1134,9 +1134,9 @@
"code": "7310373", "code": "7310373",
"name": "인천송천고등학교", "name": "인천송천고등학교",
"region": "인천광역시", "region": "인천광역시",
"sigungu": "��동구", "sigungu": "남동구",
"dong": "논현동", "dong": "논현동",
"address": "인천광역시 ��동구 앵고개로815번길 59", "address": "인천광역시 남동구 앵고개로815번길 59",
"detailAddress": "(논현동)", "detailAddress": "(논현동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -1620,7 +1620,7 @@
"address": "인천광역시 미추홀구 인하로 100", "address": "인천광역시 미추홀구 인하로 100",
"detailAddress": "(용현동, 인하대,인하공전,정석항공고)", "detailAddress": "(용현동, 인하대,인하공전,정석항공고)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여���학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://jungseok.icehs.kr" "homepage": "http://jungseok.icehs.kr"
}, },
+9 -9
View File
@@ -552,9 +552,9 @@
"code": "7310373", "code": "7310373",
"name": "인천송천고등학교", "name": "인천송천고등학교",
"region": "인천광역시", "region": "인천광역시",
"sigungu": "��동구", "sigungu": "남동구",
"dong": "논현동", "dong": "논현동",
"address": "인천광역시 ��동구 앵고개로815번길 59", "address": "인천광역시 남동구 앵고개로815번길 59",
"detailAddress": "(논현동)", "detailAddress": "(논현동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -592,7 +592,7 @@
{ {
"code": "7310449", "code": "7310449",
"name": "인천과학예술영재학교", "name": "인천과학예술영재학교",
"region": "인천광��시", "region": "인천광역시",
"sigungu": "연수구", "sigungu": "연수구",
"dong": "송도동", "dong": "송도동",
"address": "인천광역시 연수구 아카데미로 192", "address": "인천광역시 연수구 아카데미로 192",
@@ -1658,7 +1658,7 @@
"address": "인천광역시 미추홀구 인하로 100", "address": "인천광역시 미추홀구 인하로 100",
"detailAddress": "(용현동, 인하대,인하공전,정석항공고)", "detailAddress": "(용현동, 인하대,인하공전,정석항공고)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여���학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://jungseok.icehs.kr" "homepage": "http://jungseok.icehs.kr"
} }
@@ -3075,7 +3075,7 @@
"address": "인천광역시 미추홀구 인하로 100", "address": "인천광역시 미추홀구 인하로 100",
"detailAddress": "(용현동, 인하대,인하공전,정석항공고)", "detailAddress": "(용현동, 인하대,인하공전,정석항공고)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여���학", "coedu": "남여공학",
"foundation": "사립", "foundation": "사립",
"homepage": "http://jungseok.icehs.kr" "homepage": "http://jungseok.icehs.kr"
}, },
@@ -3188,7 +3188,7 @@
{ {
"code": "7310449", "code": "7310449",
"name": "인천과학예술영재학교", "name": "인천과학예술영재학교",
"region": "인천광��시", "region": "인천광역시",
"sigungu": "연수구", "sigungu": "연수구",
"dong": "송도동", "dong": "송도동",
"address": "인천광역시 연수구 아카데미로 192", "address": "인천광역시 연수구 아카데미로 192",
@@ -3422,14 +3422,14 @@
"homepage": "https://haneul.icehs.kr/" "homepage": "https://haneul.icehs.kr/"
} }
], ],
"��동구": [ "남동구": [
{ {
"code": "7310373", "code": "7310373",
"name": "인천송천고등학교", "name": "인천송천고등학교",
"region": "인천광역시", "region": "인천광역시",
"sigungu": "��동구", "sigungu": "남동구",
"dong": "논현동", "dong": "논현동",
"address": "인천광역시 ��동구 앵고개로815번길 59", "address": "인천광역시 남동구 앵고개로815번길 59",
"detailAddress": "(논현동)", "detailAddress": "(논현동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || '797ab1ac988eb27c80ae544bb6aba05c2231a9c9' }
] ]
} }
} }
+4 -1
View File
@@ -1,7 +1,10 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: /admin
User-agent: Yeti User-agent: Yeti
Allow: / Allow: /
Disallow: /admin
Sitemap: https://aeseo.site/sitemap.xml Sitemap: https://aeseo.shop/sitemap.xml
Sitemap: https://aeseo.shop/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel> <channel>
<title><![CDATA[튜터온]]></title> <title><![CDATA[다온에듀 - 인천 중등 수학 과외 · 인천 초·중·고 1:1 맞춤 과외]]></title>
<link>https://aeseo.site/</link> <link>https://aeseo.site/</link>
<description><![CDATA[인천 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description> <description><![CDATA[인천 중등 수학 과외, 인천 중등 과외, 인천 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 다온에듀]]></description>
<language>ko</language> <language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate> <lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://aeseo.site/rss.xml" rel="self" type="application/rss+xml"/> <atom:link href="https://aeseo.site/rss.xml" rel="self" type="application/rss+xml"/>
<item> <item>
<title><![CDATA[인천 초·중·고 1:1 맞춤 과외 매칭]]></title> <title><![CDATA[인천 중등 수학 과외 · 인천 중등 과외 | 초·중·고 1:1 맞춤 과외 다온에듀]]></title>
<link>https://aeseo.site/</link> <link>https://aeseo.site/</link>
<description><![CDATA[송도·청라 등 인천 전지역 과외 비용 비교 및 시범수업 신청]]></description> <description><![CDATA[인천 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/</guid> <guid>https://aeseo.site/</guid>
</item> </item>
<item> <item>
<title><![CDATA[송도동 중학생 수학과외 1:1 맞춤 수업]]></title> <title><![CDATA[인천 초등 수학 과외 · 강화읍 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%86%A1%EB%8F%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link> <link>https://aeseo.site/%EA%B0%95%ED%99%94%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[송도 학군 1:1 개념 정밀 클리닉]]></description> <description><![CDATA[인천 초등 수학 과외 및 강화읍 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%86%A1%EB%8F%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid> <guid>https://aeseo.site/%EA%B0%95%ED%99%94%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item> </item>
<item> <item>
<title><![CDATA[청라동 고등학생 영어과외 1:1 맞춤 수업]]></title> <title><![CDATA[인천 중등 영어 과외 · 교동면 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%B2%AD%EB%9D%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link> <link>https://aeseo.site/%EA%B5%90%EB%8F%99%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[청라 학군 내신 서술형 1:1 대비]]></description> <description><![CDATA[인천 중등 영어 과외 및 교동면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate> <pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%B2%AD%EB%9D%BC%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid> <guid>https://aeseo.site/%EA%B5%90%EB%8F%99%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 고등 국어 과외 · 길상면 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EA%B8%B8%EC%83%81%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[인천 고등 국어 과외 및 길상면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EA%B8%B8%EC%83%81%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 초등 과학 과외 · 내가면 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%82%B4%EA%B0%80%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[인천 초등 과학 과외 및 내가면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%82%B4%EA%B0%80%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[인천 중등 사회 과외 · 서도면 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%84%9C%EB%8F%84%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[인천 중등 사회 과외 및 서도면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%84%9C%EB%8F%84%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[인천 고등 수학 과외 · 양도면 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%96%91%EB%8F%84%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[인천 고등 수학 과외 및 양도면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%96%91%EB%8F%84%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[인천 초등 영어 과외 · 당하동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%8B%B9%ED%95%98%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[인천 초등 영어 과외 및 당하동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%8B%B9%ED%95%98%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 중등 국어 과외 · 마전동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%A7%88%EC%A0%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[인천 중등 국어 과외 및 마전동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%A7%88%EC%A0%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 고등 과학 과외 · 백석동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%B0%B1%EC%84%9D%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[인천 고등 과학 과외 및 백석동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%B0%B1%EC%84%9D%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[인천 초등 사회 과외 · 불로동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%B6%88%EB%A1%9C%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[인천 초등 사회 과외 및 불로동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%B6%88%EB%A1%9C%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[인천 중등 수학 과외 · 원당동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%9B%90%EB%8B%B9%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[인천 중등 수학 과외 및 원당동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%9B%90%EB%8B%B9%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[인천 고등 영어 과외 · 계산동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EA%B3%84%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[인천 고등 영어 과외 및 계산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EA%B3%84%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 초등 국어 과외 · 방축동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%B0%A9%EC%B6%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[인천 초등 국어 과외 및 방축동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%B0%A9%EC%B6%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[인천 중등 과학 과외 · 병방동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EB%B3%91%EB%B0%A9%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[인천 중등 과학 과외 및 병방동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EB%B3%91%EB%B0%A9%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[인천 고등 사회 과외 · 서운동 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.site/%EC%84%9C%EC%9A%B4%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[인천 고등 사회 과외 및 서운동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.site/%EC%84%9C%EC%9A%B4%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item> </item>
</channel> </channel>
</rss> </rss>
File diff suppressed because it is too large Load Diff
+1026 -1881
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.site'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+16 -29
View File
@@ -4,8 +4,11 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.site' const baseUrl = config.public.siteDomain || 'https://aeseo.site'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }> const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0] const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = { const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`, '수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`, '영어': `${baseUrl}/img/english-tutoring.png`,
@@ -13,46 +16,30 @@ export default defineEventHandler((event) => {
'과학': `${baseUrl}/img/science-tutoring.png`, '과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png` '사회': `${baseUrl}/img/social-studies-tutoring.png`
} }
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n` let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n` xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인 (대표 이미지 + 5대 과목 이미지 모두 포함) // 메인
xml += ` <url>\n` xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <loc>${baseUrl}/</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>daily</changefreq>\n`
xml += ` <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
subjects.forEach(sub => { xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n` xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
}) xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n` xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성 // 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
list.forEach(r => { uniqueDongs.forEach(dong => {
// 1. 동 대표 // 1. 동 대표
xml += ` <url>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.8</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 2. 동/학년/과목 대표 조합 // 2. 동/학년/과목 조합
grades.forEach(g => { grades.forEach(g => {
subjects.forEach(sub => { subjects.forEach(sub => {
xml += ` <url>\n` const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n` xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.7</priority>\n`
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n`
xml += ` </url>\n`
}) })
}) })
}) })
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) { } else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/) tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
} }
tokens = tokens.map(t => t.trim()).filter(Boolean) tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생' let detectedGrade = '중학생'
let detectedGradeShort = '중등' let detectedGradeShort = '중등'
let detectedSubjectKey = 'english' let detectedSubjectKey = 'math'
let detectedSubjectName = '영어' let detectedSubjectName = '수학'
let detectedTerm = '과외' let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭 // 1. 토큰들 중 동 이름 매칭
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token) const reg = getRegionByDong(token)
if (reg) { if (reg) {
matchedRegion = reg matchedRegion = reg
matchedTokenIdx = i
break break
} }
} }
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색 // 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) { if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token))) const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) { if (found) {
matchedRegion = found matchedRegion = found
matchedTokenIdx = i
break break
} }
} }
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} }
} }
// 3. 학년 추출 // 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false let hasExplicitGrade = false
const fullText = tokens.join(' ') const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) { for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) { if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper' import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper' import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = { const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png', math: '/img/math-tutoring.png',
@@ -22,18 +22,23 @@ const GRADE_LIST = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '튜터온', siteDomain = 'https://aeseo.site') { export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '튜터온', siteDomain = 'https://aeseo.site') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = (sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() || '인천' const shortSido = getShortSido(sido) || '인천'
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등') const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong) const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png' const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
const pageTitle = isRegionOnly const pageTitle = isRegionOnly
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외` ? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
const pageDescription = isRegionOnly const pageDescription = isRegionOnly
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교를 확인하세요.` ? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교(${rotatedSubText})를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.` : `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}` const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}`
const canonicalUrl = isRegionOnly const canonicalUrl = isRegionOnly
+128 -38
View File
@@ -1,22 +1,49 @@
<template> <template>
<footer class="site-footer"> <footer class="royal-footer">
<div class="footer-gold-divider"></div>
<div class="footer-container"> <div class="footer-container">
<div class="footer-info"> <!-- 1열: ROYAL CLASS 브랜드 -->
<h4 class="footer-brand">{{ siteName }}</h4> <div class="footer-col brand-col">
<div class="flex items-center gap-2 mb-2.5">
<i class="fa-solid fa-crown text-[#d4b068] text-sm"></i>
<h4 class="footer-brand font-royal-serif">ROYAL CLASS</h4>
</div>
<p class="footer-meta"> <p class="footer-meta">
초·중·고 1:1 맞춤 방문과외 및 실시간 화상코칭 서비스 | 대표번호: 010-5961-5807 {{ siteName }}는 서울 1%를 위한 프리미엄 1:1 맞춤 방문과외 및 실시간 화상 코칭 서비스를 제공합니다.
</p>
<p class="footer-copy">
Copyright © 2026 {{ siteName }}. All rights reserved.
</p> </p>
</div> </div>
<div class="footer-links"> <!-- 2열: Contact Number -->
<a href="#consult-section" class="f-link">무료상담신청</a> <div class="footer-col contact-col">
<span class="f-divider">|</span> <span class="footer-col-label">Contact Number</span>
<span class="f-link">이용약관</span> <a href="tel:010-5961-5807" class="footer-phone">
<span class="f-divider">|</span> <i class="fa-solid fa-phone text-[#d4b068] text-base mr-2"></i>
<span class="f-link"><strong>개인정보처리방침</strong></span> <span>010-5961-5807</span>
</a>
<p class="footer-hours">연중무휴 학습진단 및 시범수업 상담 (09:00 ~ 23:00)</p>
</div>
<!-- 3열: Official Company Info -->
<div class="footer-col info-col">
<span class="footer-col-label">Official Company Info</span>
<ul class="footer-info-list">
<li>서비스명: {{ siteName }} (교육청 등록 협력)</li>
<li>직통번호: 010-5961-5807</li>
<li>서울 본원: 서울특별시 강남구 테헤란로 123 비전타워 4층</li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div class="footer-bottom-inner">
<p class="footer-copy">Copyright © {{ siteName }}. All rights reserved.</p>
<div class="footer-links">
<a href="#consult-section" class="f-link">무료상담신청</a>
<span class="f-divider">|</span>
<span class="f-link">이용약관</span>
<span class="f-divider">|</span>
<span class="f-link"><strong>개인정보처리방침</strong></span>
</div>
</div> </div>
</div> </div>
</footer> </footer>
@@ -28,64 +55,127 @@ const siteName = config.public.siteName || '로열클래스 1:1 과외'
</script> </script>
<style scoped> <style scoped>
.site-footer { .royal-footer {
background: var(--footer-bg, #0f172a); background: #0c121e;
color: #94a3b8; color: #94a3b8;
padding: clamp(30px, 5vw, 50px) 16px; position: relative;
font-size: clamp(0.72rem, 1.8vw, 0.82rem); font-size: 0.82rem;
border-top: 1px solid rgba(255, 255, 255, 0.08); }
.footer-gold-divider {
height: 1px;
width: 100%;
background: linear-gradient(90deg, transparent 0%, rgba(212, 176, 104, 0.45) 50%, transparent 100%);
} }
.footer-container { .footer-container {
max-width: 1080px; max-width: 1080px;
margin: 0 auto; margin: 0 auto;
display: flex; padding: clamp(36px, 5vw, 56px) 20px clamp(28px, 4vw, 40px);
justify-content: space-between; display: grid;
align-items: flex-start; grid-template-columns: 1.3fr 1fr 1.2fr;
gap: 24px; gap: 32px;
flex-wrap: wrap;
}
.footer-info {
flex: 1 1 500px;
} }
.footer-brand { .footer-brand {
font-size: clamp(1rem, 2.4vw, 1.15rem); font-size: 1.2rem;
font-weight: 800; font-weight: 800;
color: #ffffff; letter-spacing: 0.12em;
margin-bottom: 8px; color: #e2c687;
margin: 0;
} }
.footer-meta { .footer-meta {
line-height: 1.6; color: #94a3b8;
margin-bottom: 12px; line-height: 1.65;
word-break: keep-all; word-break: keep-all;
} }
.footer-col-label {
display: block;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #cbd5e1;
margin-bottom: 10px;
}
.footer-phone {
display: inline-flex;
align-items: center;
font-size: 1.35rem;
font-weight: 900;
color: #ffffff;
text-decoration: none;
letter-spacing: 0.02em;
margin-bottom: 6px;
}
.footer-phone:hover {
color: #e2c687;
}
.footer-hours {
font-size: 0.76rem;
color: #64748b;
}
.footer-info-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 5px;
color: #94a3b8;
font-size: 0.78rem;
}
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.07);
padding: 18px 20px;
}
.footer-bottom-inner {
max-width: 1080px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.footer-copy { .footer-copy {
color: #64748b; color: #64748b;
font-size: 0.75rem;
} }
.footer-links { .footer-links {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
flex-shrink: 0;
} }
.f-link { .f-link {
color: #cbd5e1; color: #94a3b8;
text-decoration: none;
font-size: 0.76rem;
}
.f-link:hover {
color: #e2c687;
} }
.f-divider { .f-divider {
color: #475569; color: #334155;
} }
@media (max-width: 640px) { @media (max-width: 768px) {
.footer-container { .footer-container {
flex-direction: column; grid-template-columns: 1fr;
gap: 16px; gap: 24px;
} }
} }
</style> </style>
+72 -27
View File
@@ -1,26 +1,50 @@
<template> <template>
<header class="bg-white/95 backdrop-blur-md border-b border-slate-200/80 sticky top-0 z-40 w-full transition-all"> <header class="royal-header sticky top-0 z-50 w-full">
<div class="max-w-6xl mx-auto px-4 sm:px-6 py-3.5 flex items-center justify-between"> <!-- 상단 골드 헤어라인 -->
<NuxtLink to="/" class="flex items-center gap-3 no-underline"> <div class="gold-top-line"></div>
<div class="w-9 h-9 rounded-xl bg-blue-600 flex items-center justify-center text-white font-extrabold text-base shadow-sm">
{{ logoLetter }} <div class="max-w-6xl mx-auto px-4 sm:px-6 py-3 flex items-center justify-between">
</div> <!-- 좌측: 네비게이션 메뉴 (참고 시안 그대로 배치) -->
<div class="flex items-center gap-2"> <nav class="hidden md:flex items-center gap-6 text-xs lg:text-sm font-medium text-slate-300" aria-label="주요 메뉴">
<span class="font-extrabold text-lg sm:text-xl tracking-tight text-slate-900">{{ currentSiteName }}</span> <NuxtLink to="/" class="nav-item hover:text-[#e2c687] transition no-underline">홈</NuxtLink>
<span class="hidden sm:inline-flex items-center text-[11px] font-semibold text-blue-700 bg-blue-50 px-2.5 py-0.5 rounded-full border border-blue-100"> <NuxtLink :to="isHome ? '#usp-section' : '/#usp-section'" class="nav-item hover:text-[#e2c687] transition no-underline">특장점</NuxtLink>
교육청 등록 협력 <a href="#tutor-section" class="nav-item hover:text-[#e2c687] transition no-underline">강사진</a>
</span> <a href="#process-section" class="nav-item hover:text-[#e2c687] transition no-underline">프로세스</a>
<a href="#stories-section" class="nav-item hover:text-[#e2c687] transition no-underline">성공사례</a>
</nav>
<!-- 모바일용 좌측 전화 아이콘 -->
<a href="tel:010-5961-5807" class="md:hidden inline-flex items-center gap-1.5 text-xs font-semibold text-[#e2c687] no-underline">
<i class="fa-solid fa-phone"></i>
<span>상담직통</span>
</a>
<!-- 중앙: 왕관 엠블럼 & ROYAL CLASS 로열클래스 로고 -->
<NuxtLink to="/" class="emblem-link flex flex-col items-center justify-center text-center no-underline group">
<div class="crown-icon-wrap flex items-center justify-center mb-0.5">
<i class="fa-solid fa-crown text-[#d4b068] text-base sm:text-lg drop-shadow"></i>
</div> </div>
<span class="font-royal-serif text-[11px] sm:text-xs font-bold tracking-[0.22em] text-[#e2c687] leading-none">
ROYAL CLASS
</span>
<span class="text-[11px] sm:text-xs font-extrabold text-white/90 tracking-widest mt-0.5">
{{ currentSiteName }}
</span>
</NuxtLink> </NuxtLink>
<div class="flex items-center gap-4"> <!-- 우측: 상담신청 텍스트 링크 + 골드 필 버튼 -->
<a href="tel:010-5961-5807" class="hidden sm:flex items-center gap-1.5 text-sm font-semibold text-slate-600 hover:text-blue-600 transition no-underline"> <div class="flex items-center gap-3 sm:gap-5">
<i class="fa-solid fa-phone text-blue-600 text-xs"></i> <a
<span>010-5961-5807</span> href="tel:010-5961-5807"
class="hidden sm:inline-flex items-center gap-1.5 text-xs sm:text-sm font-semibold text-slate-200 hover:text-[#e2c687] transition no-underline"
>
<span>상담신청</span>
</a> </a>
<a href="#consult-section" class="bg-blue-600 hover:bg-blue-700 text-white text-xs sm:text-sm font-bold px-4 sm:px-5 py-2.5 rounded-xl transition shadow-sm hover:shadow flex items-center gap-1.5 no-underline"> <a
<span>무료 시범수업 신청</span> :href="consultHref"
<i class="fa-solid fa-arrow-right text-[11px]"></i> class="gold-cta-btn text-xs sm:text-sm font-extrabold px-4 sm:px-5 py-2 sm:py-2.5 rounded-lg transition shadow-md flex items-center gap-1.5 no-underline"
>
<span>무료 상담 신청</span>
</a> </a>
</div> </div>
</div> </div>
@@ -34,22 +58,43 @@ const props = defineProps({
siteName: { siteName: {
type: String, type: String,
default: '' default: ''
},
consultHref: {
type: String,
default: '#consult-section'
} }
}) })
const config = useRuntimeConfig() const config = useRuntimeConfig()
const route = useRoute()
const isHome = computed(() => route.path === '/')
const currentSiteName = computed(() => { const currentSiteName = computed(() => {
return props.siteName || config.public?.siteName || '1:1 맞춤과외' return props.siteName || config.public?.siteName || '로열클래스 1:1 과외'
})
const logoLetter = computed(() => {
const name = currentSiteName.value.trim()
return name.charAt(0) || 'T'
}) })
</script> </script>
<style scoped> <style scoped>
header { .royal-header {
font-family: inherit; background: linear-gradient(180deg, #0d1424 0%, #121c31 100%);
border-bottom: 1px solid rgba(212, 176, 104, 0.25);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);
}
.gold-top-line {
height: 2px;
width: 100%;
background: linear-gradient(90deg, rgba(201, 165, 92, 0.2) 0%, #e2c687 50%, rgba(201, 165, 92, 0.2) 100%);
}
.gold-cta-btn {
background: linear-gradient(135deg, #e2c687 0%, #c49b50 100%);
color: #0d1424;
border: 1px solid #ebd5a4;
}
.gold-cta-btn:hover {
background: linear-gradient(135deg, #ebd5a4 0%, #d4ab60 100%);
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(201, 165, 92, 0.35);
} }
</style> </style>
+18 -17
View File
@@ -220,21 +220,22 @@ const resetForm = () => {
<style scoped> <style scoped>
.consult-section { .consult-section {
width: 100%; width: 100%;
padding: clamp(35px, 6vw, 65px) 16px; padding: clamp(48px, 6vw, 76px) 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: transparent; background: #10192b;
border-top: 2px solid #c9a55c;
} }
.consult-card { .consult-card {
width: 100%; width: 100%;
max-width: 580px; max-width: 600px;
background: #ffffff; background: #ffffff;
border: 1px solid #e2e8f0; border: 2px solid #d4b068;
border-radius: 14px; border-radius: 16px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08); box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
padding: clamp(24px, 5vw, 40px) clamp(20px, 4vw, 36px); padding: clamp(26px, 5vw, 42px) clamp(20px, 4vw, 38px);
box-sizing: border-box; box-sizing: border-box;
} }
@@ -365,20 +366,20 @@ const resetForm = () => {
padding: 16px 20px; padding: 16px 20px;
font-size: 1.08rem; font-size: 1.08rem;
font-weight: 800; font-weight: 800;
color: #ffffff; color: #0d1424;
background: #1e3a8a; background: linear-gradient(135deg, #e5c889 0%, #c49b50 100%);
border: none; border: 1px solid #f2dfb3;
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
letter-spacing: -0.3px; letter-spacing: -0.3px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); box-shadow: 0 4px 14px rgba(196, 155, 80, 0.3);
} }
.btn-submit:hover:not(:disabled) { .btn-submit:hover:not(:disabled) {
background: #0f172a; background: linear-gradient(135deg, #f0d79e 0%, #d4ab5f 100%);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16); box-shadow: 0 6px 18px rgba(196, 155, 80, 0.45);
} }
.btn-submit:disabled { .btn-submit:disabled {
@@ -393,9 +394,9 @@ const resetForm = () => {
font-size: 1.02rem; font-size: 1.02rem;
font-weight: 800; font-weight: 800;
text-align: center; text-align: center;
color: #1e3a8a; color: #10192b;
background: #f8fafc; background: #fbf8f3;
border: 2px solid #1e3a8a; border: 2px solid #10192b;
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
@@ -405,7 +406,7 @@ const resetForm = () => {
} }
.btn-call:hover { .btn-call:hover {
background: #f1f5f9; background: #f4ece0;
} }
.success-box { .success-box {
+148 -63
View File
@@ -1,21 +1,32 @@
<template> <template>
<section class="hero-banner hero-banner-clean bg-white border-b border-slate-200/80 pt-10 sm:pt-14 pb-14 sm:pb-18 px-4 sm:px-6 relative overflow-hidden text-center" itemscope itemtype="https://schema.org/Course"> <section class="royal-sub-hero relative overflow-hidden text-center" itemscope itemtype="https://schema.org/Course">
<!-- 은은한 상단 엠비언트 라이트 --> <!-- 배경: 서울 통창 스카이라인 라운지 이미지 & 다크 네이비 오버레이 -->
<div class="absolute inset-0 bg-gradient-to-b from-blue-50/40 via-transparent to-transparent pointer-events-none"></div> <div class="hero-bg-wrap">
<img
src="/img/hero-seoul-bg.jpg"
:alt="`${shortSidoName} ${gradeShortName} ${subjectName} 과외 · ${region.dong} ${grade} ${subjectName}과외 스터디 라운지`"
class="hero-bg-img"
width="1920"
height="1080"
decoding="async"
fetchpriority="high"
/>
<div class="hero-bg-overlay"></div>
</div>
<div class="max-w-4xl mx-auto relative z-10"> <div class="max-w-4xl mx-auto relative z-10 px-4 sm:px-6 pt-12 sm:pt-16 pb-16 sm:pb-20">
<!-- 신뢰 보증 뱃지 --> <!-- 신뢰 보증 크라운 뱃지 -->
<div class="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-semibold bg-slate-50 border border-slate-200 text-slate-700 shadow-xs mb-5"> <div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-xs font-bold bg-[#10192b]/85 border border-[#c9a55c]/50 text-[#e2c687] shadow-md mb-5 backdrop-blur-xs">
<span class="w-2 h-2 rounded-full bg-blue-600"></span> <i class="fa-solid fa-crown text-[#d4b068] text-xs"></i>
<span>{{ region.dong }} 학군 전담 | 서울대·연고대 및 베테랑 강사진 100% 신원 보증</span> <span>{{ region.dong }} 학군 전담 | 서울대·연고대 및 베테랑 강사진 100% 신원 보증</span>
</div> </div>
<!-- 네이버 상위 노출 순서: 1. H1 ➔ 2. meta ➔ 3. H2 ➔ 4. 일반글 P --> <!-- 네이버 상위 노출 순서: 1. H1 ➔ 2. meta ➔ 3. H2 ➔ 4. 일반글 P -->
<h1 itemprop="headline" class="text-2xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-slate-900 leading-[1.22] sm:leading-[1.18] mb-3"> <h1 itemprop="headline" class="text-2xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-white leading-[1.24] sm:leading-[1.2] mb-3">
<span class="highlight text-blue-600">{{ shortSidoName }} {{ gradeShortName }} {{ subjectName }} 과외 · {{ region.dong }} {{ grade }} {{ subjectName }}과외</span><br /> <span class="gold-gradient-text font-royal-serif">{{ shortSidoName }} {{ gradeShortName }} {{ subjectName }} 과외 · {{ region.dong }} {{ grade }} {{ subjectName }}과외</span><br />
1:1 맞춤 학습으로 성적 역전! <span class="text-white">1:1 맞춤 학습으로 성적 역전!</span>
</h1> </h1>
<meta itemprop="name" :content="`${shortSidoName} ${gradeShortName} ${subjectName} 과외 · ${shortSidoName} ${gradeShortName} 과외 (${region.dong})`" /> <meta itemprop="name" :content="`${shortSidoName} ${gradeShortName} ${subjectName} 과외 · ${shortSidoName} ${gradeShortName} 과외 (${region.dong})`" />
<meta itemprop="description" :content="`${shortSidoName} ${gradeShortName} ${subjectName} 과외 및 ${shortSidoName} ${gradeShortName} 과외 전문 매칭. ${displayRegionName} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업`" /> <meta itemprop="description" :content="`${shortSidoName} ${gradeShortName} ${subjectName} 과외 및 ${shortSidoName} ${gradeShortName} 과외 전문 매칭. ${displayRegionName} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업`" />
@@ -24,75 +35,80 @@
<h2 class="hero-subtitle"> <h2 class="hero-subtitle">
{{ shortSidoName }} {{ gradeShortName }} 과외 · {{ displayRegionName }} {{ gradeShortName }} {{ subjectName }} 과외 검증된 전문 선생님 1:1 매칭 {{ shortSidoName }} {{ gradeShortName }} 과외 · {{ displayRegionName }} {{ gradeShortName }} {{ subjectName }} 과외 검증된 전문 선생님 1:1 매칭
</h2> </h2>
<p class="sub-headline text-xs sm:text-base text-slate-600 font-normal max-w-2xl mx-auto leading-relaxed mb-7"> <p class="sub-headline text-xs sm:text-base text-slate-200/90 font-normal max-w-2xl mx-auto leading-relaxed mb-7">
{{ shortSidoName }} {{ gradeShortName }} {{ subjectName }} 과외 및 {{ shortSidoName }} {{ gradeShortName }} 과외를 찾으시는 학부모님을 위해 {{ region.dong }} 인근 학교별 기출 족보 분석부터 개인별 취약 개념 완벽 보완, 투명한 과외비 안내와 무료 시범수업까지 1:1 밀착 커리큘럼을 제공합니다. {{ shortSidoName }} {{ gradeShortName }} {{ subjectName }} 과외 및 {{ shortSidoName }} {{ gradeShortName }} 과외를 찾으시는 학부모님을 위해 {{ region.dong }} 인근 학교별 기출 족보 분석부터 개인별 취약 개념 완벽 보완, 투명한 과외비 안내와 무료 시범수업까지 1:1 밀착 커리큘럼을 제공합니다.
</p> </p>
<!-- 4대 핵심 신뢰 지표 바 --> <!-- 4대 핵심 신뢰 지표 바 (다크 글래스 & 골드 보더) -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-2.5 sm:gap-3 max-w-3xl mx-auto mb-7 text-left"> <div class="grid grid-cols-2 md:grid-cols-4 gap-2.5 sm:gap-3 max-w-3xl mx-auto mb-7 text-left">
<div class="bg-slate-50 border border-slate-200/90 rounded-xl p-3 flex items-start gap-2.5"> <div class="trust-glass-card">
<i class="fa-solid fa-circle-check text-blue-600 text-sm sm:text-base mt-0.5 shrink-0"></i> <i class="fa-solid fa-circle-check text-[#e2c687] text-sm sm:text-base mt-0.5 shrink-0"></i>
<div> <div>
<strong class="block text-xs sm:text-sm font-bold text-slate-900">100% 서류 검증</strong> <strong class="block text-xs sm:text-sm font-bold text-white">100% 서류 검증</strong>
<span class="text-[11px] text-slate-500 leading-tight">학력·경력 증명서 인증</span> <span class="text-[11px] text-slate-300 leading-tight">학력·경력 증명서 인증</span>
</div> </div>
</div> </div>
<div class="bg-slate-50 border border-slate-200/90 rounded-xl p-3 flex items-start gap-2.5"> <div class="trust-glass-card">
<i class="fa-solid fa-circle-check text-blue-600 text-sm sm:text-base mt-0.5 shrink-0"></i> <i class="fa-solid fa-circle-check text-[#e2c687] text-sm sm:text-base mt-0.5 shrink-0"></i>
<div> <div>
<strong class="block text-xs sm:text-sm font-bold text-slate-900">무료 시범수업</strong> <strong class="block text-xs sm:text-sm font-bold text-white">무료 시범수업</strong>
<span class="text-[11px] text-slate-500 leading-tight">직접 받아보고 선택</span> <span class="text-[11px] text-slate-300 leading-tight">직접 받아보고 선택</span>
</div> </div>
</div> </div>
<div class="bg-slate-50 border border-slate-200/90 rounded-xl p-3 flex items-start gap-2.5"> <div class="trust-glass-card">
<i class="fa-solid fa-circle-check text-blue-600 text-sm sm:text-base mt-0.5 shrink-0"></i> <i class="fa-solid fa-circle-check text-[#e2c687] text-sm sm:text-base mt-0.5 shrink-0"></i>
<div> <div>
<strong class="block text-xs sm:text-sm font-bold text-slate-900">과외 비용 투명 시세</strong> <strong class="block text-xs sm:text-sm font-bold text-white">과외 비용 투명 시세</strong>
<span class="text-[11px] text-slate-500 leading-tight">동네 표준 시세 가이드</span> <span class="text-[11px] text-slate-300 leading-tight">동네 표준 시세 가이드</span>
</div> </div>
</div> </div>
<div class="bg-slate-50 border border-slate-200/90 rounded-xl p-3 flex items-start gap-2.5"> <div class="trust-glass-card">
<i class="fa-solid fa-circle-check text-blue-600 text-sm sm:text-base mt-0.5 shrink-0"></i> <i class="fa-solid fa-circle-check text-[#e2c687] text-sm sm:text-base mt-0.5 shrink-0"></i>
<div> <div>
<strong class="block text-xs sm:text-sm font-bold text-slate-900">학교별 기출 분석</strong> <strong class="block text-xs sm:text-sm font-bold text-white">학교별 기출 분석</strong>
<span class="text-[11px] text-slate-500 leading-tight">{{ region.dong }} 3개년 족보 완파</span> <span class="text-[11px] text-slate-300 leading-tight">{{ region.dong }} 3개년 족보 완파</span>
</div> </div>
</div> </div>
</div> </div>
<!-- CTA 그룹 --> <!-- 시안 스타일 듀얼 CTA 버튼 그룹 -->
<div class="flex flex-col items-center gap-3.5 mb-6"> <div class="flex flex-col items-center gap-3.5 mb-6">
<a <div class="flex items-center justify-center gap-3 flex-wrap w-full">
href="#consult-section" <a
class="inline-flex items-center justify-center gap-2 w-full max-w-md py-4 px-6 bg-blue-600 hover:bg-blue-700 text-white font-extrabold text-sm sm:text-base rounded-xl shadow-md hover:shadow-lg transition-all" href="#consult-section"
> class="btn-hero-gold inline-flex items-center justify-center gap-2 py-3.5 px-7 font-extrabold text-sm sm:text-base rounded-lg shadow-lg transition-all no-underline"
<i class="fa-solid fa-file-signature text-base"></i> >
<span>{{ region.dong }} 과외 시세 확인 & 무료 시범수업 신청</span> <i class="fa-solid fa-crown text-sm"></i>
</a> <span>{{ region.dong }} 무료 시범수업 신청</span>
<div class="flex items-center gap-2 text-xs sm:text-sm text-slate-500"> </a>
<i class="fa-solid fa-phone text-blue-600 text-xs"></i> <a
<span>실시간 학습상담 직통: <strong class="text-slate-800 font-bold">010-5961-5807</strong></span> href="tel:010-5961-5807"
class="btn-hero-outline inline-flex items-center justify-center gap-2 py-3.5 px-6 font-bold text-sm sm:text-base rounded-lg transition-all no-underline"
>
<i class="fa-solid fa-phone text-xs text-[#e2c687]"></i>
<span>전화 상담 010-5961-5807</span>
</a>
</div> </div>
</div> </div>
<!-- 실시간 접수 롤링 바 --> <!-- 실시간 접수 롤링 바 -->
<div class="inline-flex items-center gap-2.5 bg-slate-100/90 border border-slate-200/80 rounded-full px-4 py-1.5 text-xs text-slate-600 shadow-xs mb-6"> <div class="inline-flex items-center gap-2.5 bg-[#0a101d]/80 border border-[#c9a55c]/40 rounded-full px-4 py-1.5 text-xs text-slate-200 shadow-md mb-6">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span> <span class="w-1.5 h-1.5 rounded-full bg-[#e2c687] animate-pulse"></span>
<span class="font-bold text-slate-700">실시간 매칭</span> <span class="font-bold text-[#e2c687]">실시간 매칭</span>
<span class="text-slate-300">|</span> <span class="text-slate-500">|</span>
<span class="truncate max-w-[260px] sm:max-w-md font-medium text-slate-600">{{ currentNotice }}</span> <span class="truncate max-w-[250px] sm:max-w-md font-medium text-slate-200">{{ currentNotice }}</span>
</div> </div>
<!-- 태그 목록 --> <!-- 태그 목록 -->
<div class="flex justify-center flex-wrap gap-1.5"> <div class="flex justify-center flex-wrap gap-1.5">
<span class="text-[11px] px-2.5 py-1 bg-slate-100 border border-slate-200 text-slate-600 rounded-md font-medium">#{{ shortSidoName }}{{ gradeShortName }}{{ subjectName }}과외</span> <span class="hero-tag">#{{ shortSidoName }}{{ gradeShortName }}{{ subjectName }}과외</span>
<span class="text-[11px] px-2.5 py-1 bg-slate-100 border border-slate-200 text-slate-600 rounded-md font-medium">#{{ shortSidoName }}{{ gradeShortName }}과외</span> <span class="hero-tag">#{{ shortSidoName }}{{ gradeShortName }}과외</span>
<span class="text-[11px] px-2.5 py-1 bg-slate-100 border border-slate-200 text-slate-600 rounded-md font-medium">#{{ region.dong }}과외</span> <span class="hero-tag">#{{ region.dong }}과외</span>
<span class="text-[11px] px-2.5 py-1 bg-slate-100 border border-slate-200 text-slate-600 rounded-md font-medium">#{{ region.dong }}{{ subjectName }}과외</span> <span class="hero-tag">#{{ region.dong }}{{ subjectName }}과외</span>
<span class="text-[11px] px-2.5 py-1 bg-slate-100 border border-slate-200 text-slate-600 rounded-md font-medium">#1대1무료시범수업</span> <span class="hero-tag">#로열클래스1대1과외</span>
</div> </div>
</div> </div>
@@ -101,6 +117,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
region: { region: {
@@ -126,7 +143,7 @@ const props = defineProps({
}) })
const shortSidoName = computed(() => { const shortSidoName = computed(() => {
const s = (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() const s = getShortSido(props.region?.sido || '')
return s || '서울' return s || '서울'
}) })
@@ -168,16 +185,7 @@ const displayRegionName = computed(() => {
const sido = props.region.sido || '' const sido = props.region.sido || ''
const dong = props.region.dong || '' const dong = props.region.dong || ''
if (sido && dong) { if (sido && dong) {
let shortSido = sido.slice(0, 2) return `${getShortSido(sido) || '서울'} ${dong}`
if (sido.includes('서울')) shortSido = '서울'
else if (sido.includes('대구')) shortSido = '대구'
else if (sido.includes('부산')) shortSido = '부산'
else if (sido.includes('인천')) shortSido = '인천'
else if (sido.includes('광주')) shortSido = '광주'
else if (sido.includes('대전')) shortSido = '대전'
else if (sido.includes('울산')) shortSido = '울산'
else if (sido.includes('경기')) shortSido = '경기'
return `${shortSido} ${dong}`
} }
return dong || sido return dong || sido
} }
@@ -186,15 +194,92 @@ const displayRegionName = computed(() => {
</script> </script>
<style scoped> <style scoped>
.hero-banner-clean { .royal-sub-hero {
background-color: #0d1424;
border-bottom: 2px solid #c9a55c;
word-break: keep-all; word-break: keep-all;
} }
.hero-bg-wrap {
position: absolute;
inset: 0;
z-index: 0;
}
.hero-bg-img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.hero-bg-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(10, 16, 29, 0.76) 0%,
rgba(16, 25, 43, 0.72) 50%,
rgba(10, 16, 29, 0.9) 100%
);
}
.gold-gradient-text {
background: linear-gradient(180deg, #f9ebb9 0%, #DFB970 50%, #c19344 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5));
}
.hero-subtitle { .hero-subtitle {
font-size: 1.15rem; font-size: 1.1rem;
font-weight: 700; font-weight: 700;
color: #334155; color: #ebd5a4;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
line-height: 1.5; line-height: 1.5;
} }
.trust-glass-card {
background: rgba(16, 25, 43, 0.75);
border: 1px solid rgba(201, 165, 92, 0.4);
border-radius: 12px;
padding: 12px;
display: flex;
align-items: flex-start;
gap: 10px;
backdrop-filter: blur(4px);
}
.btn-hero-gold {
background: linear-gradient(135deg, #e5c889 0%, #c49b50 100%);
color: #0d1424;
border: 1px solid #f2dfb3;
}
.btn-hero-gold:hover {
background: linear-gradient(135deg, #f0d79e 0%, #d4ab5f 100%);
transform: translateY(-2px);
}
.btn-hero-outline {
background: rgba(16, 25, 43, 0.72);
color: #f3e4be;
border: 1.5px solid #c9a55c;
backdrop-filter: blur(6px);
}
.btn-hero-outline:hover {
background: rgba(201, 165, 92, 0.22);
transform: translateY(-2px);
}
.hero-tag {
font-size: 11px;
padding: 4px 10px;
background: rgba(16, 25, 43, 0.8);
border: 1px solid rgba(201, 165, 92, 0.35);
color: #e2c687;
border-radius: 6px;
font-weight: 600;
}
</style> </style>
+105 -26
View File
@@ -29,7 +29,7 @@
</div> </div>
</div> </div>
<!-- 2. 같은 동의 다른 학년 링크 --> <!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i> <i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
> >
{{ regionDong }} {{ g }} {{ subjectName }} {{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink> </NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div> </div>
</div> </div>
<!-- 3. 관내 인접 동 링크 --> <!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card"> <div class="hub-card">
<div class="card-head"> <div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i> <i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div> </div>
</nav> </nav>
</template> </template>
@@ -85,7 +120,7 @@ const props = defineProps({
}, },
subjectName: { subjectName: {
type: String, type: String,
default: '영어' default: '수학'
} }
}) })
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생'] const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개) // 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => { const nearbyDongs = computed(() => {
const curDong = props.regionDong const curDong = props.regionDong
const allList = (regionsData.list || []) const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
// 현재 동의 시군구 찾기 if (total === 0) return []
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
// 중복 제거 후 12개 반환 const curIdx = uniqueDongs.indexOf(curDong)
return Array.from(new Set(candidates)).slice(0, 12) const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
}) })
</script> </script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af; border-color: #1e40af;
font-weight: 700; font-weight: 700;
} }
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style> </style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json' import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
} }
} }
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개 // 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) { for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) { if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return list.slice(0, 6) return Array.isArray(list) ? list.slice(0, 6) : []
} }
} }
+83 -80
View File
@@ -1,138 +1,141 @@
<template> <template>
<section class="process-section"> <section id="process-section" class="royal-process-section">
<div class="section-header"> <div class="process-inner">
<span class="section-badge">쉽고 빠른 시작</span> <div class="section-header">
<h2 class="section-title">1:1 맞춤 과외 4단계 진행 프로세스</h2> <span class="section-badge">MATCHING PROCESS</span>
<p class="section-desc">무료 상담부터 모의수업까지 부담 없이 체험해보고 결정하실 수 있습니다.</p> <h2 class="section-title">
</div> 진단 테스트 및 상담 <span class="gold-arrow">➔</span> 최적의 교사 매칭 <span class="gold-arrow">➔</span> 1:1 맞춤 수업 진행
</h2>
<p class="section-desc">무료 학습 진단부터 시범수업까지 직접 경험해보고 결정하실 수 있습니다.</p>
</div>
<div class="process-flow"> <div class="process-flow">
<div class="flow-step"> <div class="flow-step">
<div class="step-num-circle">1</div> <div class="step-num font-royal-serif">1</div>
<h3 class="step-label">무료 상담 신청</h3> <h3 class="step-label">진단 테스트 및 상담</h3>
<p class="step-desc">온라인 폼 또는 전화로 학생의 지역, 학년, 희망 과목 접수</p> <p class="step-desc">온라인 또는 전화로 학생의 현재 성적, 학습 성향 및 목표 대학·학교 정밀 분석</p>
</div> </div>
<div class="flow-arrow"><i class="fa-solid fa-chevron-right"></i></div>
<div class="flow-step"> <div class="flow-arrow"><i class="fa-solid fa-arrow-right"></i></div>
<div class="step-num-circle">2</div>
<h3 class="step-label">학습 정밀 진단</h3> <div class="flow-step">
<p class="step-desc">전문 교육팀장이 유선으로 학생의 최근 성적, 성향 및 목표 분석</p> <div class="step-num font-royal-serif">2</div>
</div> <h3 class="step-label">최적의 교사 매칭</h3>
<div class="flow-arrow"><i class="fa-solid fa-chevron-right"></i></div> <p class="step-desc">학생 성향과 목표 등급에 최적화된 전담 선생님 배정 및 1회 무료 모의수업 진행</p>
<div class="flow-step"> </div>
<div class="step-num-circle">3</div>
<h3 class="step-label">1회 무료 모의수업</h3> <div class="flow-arrow"><i class="fa-solid fa-arrow-right"></i></div>
<p class="step-desc">배정된 전담 선생님과 1:1 방문 또는 화상 시범 수업 진행</p>
</div> <div class="flow-step">
<div class="flow-arrow"><i class="fa-solid fa-chevron-right"></i></div> <div class="step-num font-royal-serif">3</div>
<div class="flow-step highlight"> <h3 class="step-label">1:1 맞춤 수업 진행</h3>
<div class="step-num-circle">4</div> <p class="step-desc">선생님 확정 후 학교별 기출 족보 밀착 코칭 및 주간 메타인지 플래너 관리</p>
<h3 class="step-label">정규 수업 & 코칭</h3> </div>
<p class="step-desc">선생님 최종 확정 후 주간 플래너 및 맞춤 커리큘럼 시작</p>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<style scoped> <style scoped>
.process-section { .royal-process-section {
max-width: 1080px; background: #10192b;
color: #ffffff;
padding: clamp(48px, 6vw, 72px) 16px;
margin: 0 auto 50px; margin: 0 auto 50px;
padding: 0 16px; border-top: 1px solid rgba(212, 176, 104, 0.35);
border-bottom: 1px solid rgba(212, 176, 104, 0.35);
}
.process-inner {
max-width: 1080px;
margin: 0 auto;
} }
.section-header { .section-header {
text-align: center; text-align: center;
margin-bottom: 28px; margin-bottom: 36px;
} }
.section-badge { .section-badge {
display: inline-block; display: inline-block;
font-size: clamp(0.78rem, 2vw, 0.88rem); font-size: 0.76rem;
font-weight: 700; font-weight: 800;
color: var(--primary-color, #1e3a8a); letter-spacing: 0.22em;
background: var(--badge-bg, rgba(30, 58, 138, 0.08)); color: #e2c687;
padding: 5px 12px;
border-radius: 9999px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.section-title { .section-title {
font-size: clamp(1.3rem, 3.8vw, 1.95rem); font-size: clamp(1.25rem, 3.4vw, 1.85rem);
font-weight: 800; font-weight: 800;
color: var(--text-color, #1e293b); color: #ffffff;
letter-spacing: -0.6px; letter-spacing: -0.4px;
word-break: keep-all; word-break: keep-all;
line-height: 1.35; line-height: 1.35;
} }
.gold-arrow {
color: #e2c687;
margin: 0 4px;
}
.section-desc { .section-desc {
font-size: clamp(0.85rem, 2.2vw, 0.98rem); font-size: clamp(0.84rem, 2vw, 0.95rem);
color: #64748b; color: #cbd5e1;
margin-top: 6px; margin-top: 8px;
word-break: keep-all;
} }
.process-flow { .process-flow {
display: flex; display: flex;
align-items: center; align-items: stretch;
justify-content: space-between; justify-content: space-between;
gap: 10px; gap: 14px;
background: var(--bg-card, #ffffff);
border: 1px solid var(--border-color, #e2e8f0);
border-radius: var(--card-radius, 16px);
padding: clamp(20px, 4vw, 32px) clamp(16px, 3vw, 24px);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
} }
.flow-step { .flow-step {
flex: 1; flex: 1;
background: #ffffff;
border: 2px solid #d4b068;
border-radius: 14px;
padding: 28px 20px;
text-align: center; text-align: center;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.28);
} }
.step-num-circle { .step-num {
width: 38px; font-size: 2.3rem;
height: 38px; font-weight: 800;
border-radius: 50%; color: #c49b50;
background: var(--primary-color, #1e3a8a); line-height: 1;
color: #ffffff; margin-bottom: 12px;
font-size: 1.1rem;
font-weight: 900;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 12px;
}
.flow-step.highlight .step-num-circle {
background: var(--accent-color, #d97706);
} }
.step-label { .step-label {
font-size: clamp(0.88rem, 2.2vw, 1rem); font-size: 1.08rem;
font-weight: 800; font-weight: 800;
color: var(--text-color, #0f172a); color: #10192b;
margin-bottom: 6px; margin-bottom: 8px;
word-break: keep-all;
} }
.step-desc { .step-desc {
font-size: clamp(0.74rem, 1.8vw, 0.82rem); font-size: 0.82rem;
color: #64748b; color: #475569;
line-height: 1.45; line-height: 1.55;
word-break: keep-all; word-break: keep-all;
} }
.flow-arrow { .flow-arrow {
color: #cbd5e1; display: flex;
font-size: 1.2rem; align-items: center;
font-weight: 900; justify-content: center;
color: #e2c687;
font-size: 1.35rem;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.process-flow { .process-flow {
flex-direction: column; flex-direction: column;
gap: 18px; gap: 14px;
} }
.flow-arrow { .flow-arrow {
transform: rotate(90deg); transform: rotate(90deg);
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid"> <div class="metrics-grid">
<div class="metric-card"> <div class="metric-card">
<div class="metric-tag-text">POINT 01</div> <div class="metric-tag-text">POINT 01</div>
<div class="metric-num">98.7%</div> <div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div> <div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div> <div class="metric-sub">재수강 의사 압도적 1위</div>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-tag-text">POINT 02</div> <div class="metric-tag-text">POINT 02</div>
<div class="metric-num">+26.8점</div> <div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div> <div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div> <div class="metric-sub">수강 3개월 이내 실측</div>
</div> </div>
@@ -21,7 +21,7 @@
</div> </div>
<div class="metric-card"> <div class="metric-card">
<div class="metric-tag-text">POINT 04</div> <div class="metric-tag-text">POINT 04</div>
<div class="metric-num">12,500+</div> <div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div> <div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div> <div class="metric-sub">전국 학군별 데이터 기반</div>
</div> </div>
@@ -30,12 +30,22 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { computed } from 'vue'
const props = defineProps({
regionDong: { regionDong: {
type: String, type: String,
default: '해당 지역' default: '해당 지역'
} }
}) })
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script> </script>
<style scoped> <style scoped>
+248 -81
View File
@@ -1,24 +1,97 @@
<template> <template>
<section class="stories-section"> <section id="stories-section" class="stories-section">
<div class="section-header"> <div class="stories-inner">
<span class="section-badge">{{ regionDong }} {{ grade }} {{ subjectName }} 성공 사례</span> <div class="section-header">
<h2 class="section-title">{{ regionDong }} {{ grade }}들의 놀라운 {{ subjectName }} 성적 변화</h2> <span class="section-badge">REVIEWS &amp; SUCCESS STORIES</span>
<p class="section-desc">공부 방법만 1:1로 정확히 교정해도 성적은 반드시 오릅니다. 실제 수강생들의 리얼 후기를 확인하세요.</p> <h2 class="section-title">{{ regionDong }} {{ grade }}들의 놀라운 {{ subjectName }} 성적 변화</h2>
</div> <p class="section-desc">공부 방법만 1:1로 정확히 교정해도 성적은 반드시 오릅니다. 실제 수강생들의 리얼 후기와 상승 지표를 확인하세요.</p>
</div>
<!-- 학년과 과목에 100% 일치하는 맞춤형 성적 향상 수기 카드 3선 --> <!-- 시안 5번째 섹션 레이아웃: 좌측 리뷰 카드 + 우측 98% 성적 상승 그래프 카드 -->
<div class="stories-grid"> <div class="reviews-layout-grid">
<div v-for="(story, idx) in matchedStories" :key="idx" class="story-card"> <div class="reviews-stack">
<div class="story-top"> <div v-for="(story, idx) in matchedStories.slice(0, 2)" :key="idx" class="story-card">
<span class="student-info">{{ story.student }}</span> <div class="story-top">
<div class="grade-jump"> <div>
<span class="before">{{ story.before }}</span> <span class="student-info">{{ story.student }}</span>
<span class="arrow">→</span> <div class="grade-jump">
<span class="after">{{ story.after }}</span> <span class="before">{{ story.before }}</span>
<span class="arrow">➔</span>
<span class="after">{{ story.after }}</span>
</div>
</div>
<div class="stars" aria-label="별점 5점 만점">
<i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i>
</div>
</div>
<p class="story-review">"{{ story.review }}"</p>
<div class="story-sub">수강 기간: {{ story.period }} / {{ regionDong }} 1:1 맞춤 {{ subjectName }} 수업</div>
</div>
</div>
<!-- 우측: 세 번째 스토리 + 98% 상승 그래프 카드 -->
<div class="graph-story-card">
<div v-if="matchedStories[2]" class="graph-story-head">
<div class="story-top">
<div>
<span class="student-info">{{ matchedStories[2].student }}</span>
<div class="grade-jump">
<span class="before">{{ matchedStories[2].before }}</span>
<span class="arrow">➔</span>
<span class="after">{{ matchedStories[2].after }}</span>
</div>
</div>
<div class="stars" aria-label="별점 5점 만점">
<i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i><i class="fa-solid fa-star"></i>
</div>
</div>
<p class="story-review mb-4">"{{ matchedStories[2].review }}"</p>
</div>
<div class="chart-wrap">
<div class="chart-caption">
<strong>{{ regionDong }} {{ subjectName }} 수강생 평균 성적 향상 추이</strong>
<span class="chart-highlight">성적 향상도 98%</span>
</div>
<svg viewBox="0 0 500 190" class="chart-svg" role="img" :aria-label="`${regionDong} ${subjectName} 1개월부터 6개월까지 98% 성적 향상 그래프`">
<line x1="30" y1="30" x2="470" y2="30" stroke="#e5dec9" stroke-dasharray="3 3" />
<line x1="30" y1="75" x2="470" y2="75" stroke="#e5dec9" stroke-dasharray="3 3" />
<line x1="30" y1="120" x2="470" y2="120" stroke="#e5dec9" stroke-dasharray="3 3" />
<line x1="30" y1="160" x2="470" y2="160" stroke="#cbbba0" stroke-width="1.5" />
<rect x="52" y="122" width="26" height="38" rx="4" fill="#d9cebe" />
<rect x="128" y="95" width="26" height="65" rx="4" fill="#cfc1ab" />
<rect x="204" y="108" width="26" height="52" rx="4" fill="#cfc1ab" />
<rect x="280" y="72" width="26" height="88" rx="4" fill="#c2ae8e" />
<rect x="356" y="84" width="26" height="76" rx="4" fill="#b89f78" />
<rect x="430" y="32" width="30" height="128" rx="4" fill="#10192b" />
<polyline
fill="none"
stroke="#c49b50"
stroke-width="3"
points="65,118 141,88 217,102 293,65 369,78 445,26"
/>
<circle cx="65" cy="118" r="4" fill="#ffffff" stroke="#c49b50" stroke-width="2.5" />
<circle cx="141" cy="88" r="4" fill="#ffffff" stroke="#c49b50" stroke-width="2.5" />
<circle cx="217" cy="102" r="4" fill="#ffffff" stroke="#c49b50" stroke-width="2.5" />
<circle cx="293" cy="65" r="4" fill="#ffffff" stroke="#c49b50" stroke-width="2.5" />
<circle cx="369" cy="78" r="4" fill="#ffffff" stroke="#c49b50" stroke-width="2.5" />
<circle cx="445" cy="26" r="5.5" fill="#e2c687" stroke="#10192b" stroke-width="2.5" />
<rect x="418" y="2" width="54" height="19" rx="5" fill="#10192b" />
<text x="445" y="15" text-anchor="middle" fill="#e2c687" font-size="11" font-weight="800">98%</text>
<text x="65" y="180" text-anchor="middle" fill="#64748b" font-size="11" font-weight="600">1개월</text>
<text x="141" y="180" text-anchor="middle" fill="#64748b" font-size="11" font-weight="600">2개월</text>
<text x="217" y="180" text-anchor="middle" fill="#64748b" font-size="11" font-weight="600">3개월</text>
<text x="293" y="180" text-anchor="middle" fill="#64748b" font-size="11" font-weight="600">4개월</text>
<text x="369" y="180" text-anchor="middle" fill="#64748b" font-size="11" font-weight="600">5개월</text>
<text x="445" y="180" text-anchor="middle" fill="#10192b" font-size="11" font-weight="800">6개월</text>
</svg>
</div> </div>
</div> </div>
<p class="story-review">"{{ story.review }}"</p>
<div class="story-sub">수강 기간: {{ story.period }} / {{ regionDong }} 1:1 맞춤 {{ subjectName }} 수업</div>
</div> </div>
</div> </div>
</section> </section>
@@ -26,6 +99,7 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({ const props = defineProps({
regionDong: { regionDong: {
@@ -42,60 +116,98 @@ const props = defineProps({
} }
}) })
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성 const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => { const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등') const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong const isElem = (props.grade || '').includes('초등')
const sub = props.subjectName const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) { if (isHigh) {
// 고등학생 전용 스토리 const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [ return [
{ {
student: `${dong} 고2 김○○ 학생`, student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (56점)`, before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (96점)`, after: `내신 1등급 (${afterScore}점)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!` review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
}, },
{ {
student: `${dong} 고1 박○○ 학생`, student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`, before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`, after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: '4개월', period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.` review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
}, },
{ {
student: `${dong} 고3 이○○ 학부모님`, student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 수능 모평 3등급`, before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`, after: `수능 1등급 최저 충족`,
period: '6개월', period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!` review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
} }
] ]
} else { } else {
// 중학생 전용 스토리 const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [ return [
{ {
student: `${dong} 중2 이○○ 학생`, student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} 68점 (C등급)`, before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 98점 (A등급)`, after: `중간·기말 ${afterScore}점 (A등급)`,
period: '3개월', period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!` review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
}, },
{ {
student: `${dong} 중3 최○○ 학생`, student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (75점)`, before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`, after: `100점 만점 달성`,
period: '4개월', period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.` review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
}, },
{ {
student: `${dong} 중1 정○○ 학부모님`, student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`, before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`, after: `스스로 매일 2시간 복습`,
period: '5개월', period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.` review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
} }
] ]
} }
@@ -104,31 +216,36 @@ const matchedStories = computed(() => {
<style scoped> <style scoped>
.stories-section { .stories-section {
max-width: 1080px; background: #f4ece0;
border-top: 1px solid #e4d9c5;
border-bottom: 1px solid #e4d9c5;
padding: clamp(48px, 6vw, 72px) 16px;
margin: 0 auto 50px; margin: 0 auto 50px;
padding: 0 16px; }
.stories-inner {
max-width: 1080px;
margin: 0 auto;
} }
.section-header { .section-header {
text-align: center; text-align: center;
margin-bottom: 28px; margin-bottom: 32px;
} }
.section-badge { .section-badge {
display: inline-block; display: inline-block;
font-size: clamp(0.78rem, 2vw, 0.88rem); font-size: 0.76rem;
font-weight: 700; font-weight: 800;
color: var(--primary-color, #1e3a8a); letter-spacing: 0.2em;
background: var(--badge-bg, rgba(30, 58, 138, 0.08)); color: #9e7a32;
padding: 5px 12px; margin-bottom: 6px;
border-radius: 9999px;
margin-bottom: 8px;
} }
.section-title { .section-title {
font-size: clamp(1.3rem, 3.8vw, 1.95rem); font-size: clamp(1.35rem, 3.8vw, 2rem);
font-weight: 800; font-weight: 900;
color: var(--text-color, #1e293b); color: #10192b;
letter-spacing: -0.6px; letter-spacing: -0.6px;
word-break: keep-all; word-break: keep-all;
line-height: 1.35; line-height: 1.35;
@@ -136,38 +253,56 @@ const matchedStories = computed(() => {
.section-desc { .section-desc {
font-size: clamp(0.85rem, 2.2vw, 0.98rem); font-size: clamp(0.85rem, 2.2vw, 0.98rem);
color: #64748b; color: #475569;
margin-top: 6px; margin-top: 6px;
word-break: keep-all; word-break: keep-all;
} }
.stories-grid { .reviews-layout-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: 5fr 7fr;
gap: 16px; gap: 20px;
align-items: stretch;
} }
.story-card { .reviews-stack {
background: var(--bg-card, #ffffff); display: flex;
border: 1px solid var(--border-color, #e2e8f0); flex-direction: column;
border-radius: var(--card-radius, 14px); gap: 18px;
padding: clamp(18px, 3vw, 24px); }
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
.story-card,
.graph-story-card {
background: #ffffff;
border: 1.5px solid #dec492;
border-radius: 16px;
padding: 22px 24px;
box-shadow: 0 8px 22px rgba(16, 25, 43, 0.05);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
} }
.story-top { .story-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 10px;
margin-bottom: 12px; margin-bottom: 12px;
} }
.stars {
color: #d4a038;
font-size: 0.78rem;
flex-shrink: 0;
}
.student-info { .student-info {
display: block; display: block;
font-size: clamp(0.82rem, 2vw, 0.9rem); font-size: 0.92rem;
font-weight: 800; font-weight: 800;
color: #475569; color: #10192b;
margin-bottom: 6px; margin-bottom: 4px;
} }
.grade-jump { .grade-jump {
@@ -178,40 +313,72 @@ const matchedStories = computed(() => {
} }
.before { .before {
font-size: 0.82rem; font-size: 0.8rem;
color: #94a3b8; color: #94a3b8;
text-decoration: line-through; text-decoration: line-through;
} }
.arrow { .arrow {
color: var(--accent-color, #d97706); color: #b58e43;
font-weight: 900; font-weight: 900;
} }
.after { .after {
font-size: clamp(0.95rem, 2.4vw, 1.1rem); font-size: 0.95rem;
font-weight: 900; font-weight: 900;
color: var(--primary-color, #1e3a8a); color: #9e7a32;
} }
.story-review { .story-review {
font-size: clamp(0.82rem, 2vw, 0.92rem); font-size: 0.86rem;
line-height: 1.6; line-height: 1.65;
color: #334155; color: #334155;
margin-bottom: 14px; margin-bottom: 12px;
word-break: keep-all; word-break: keep-all;
flex-grow: 1;
} }
.story-sub { .story-sub {
font-size: 0.75rem; font-size: 0.75rem;
color: #94a3b8; color: #94a3b8;
border-top: 1px dashed var(--border-color, #e2e8f0); border-top: 1px dashed #e4d9c5;
padding-top: 10px; padding-top: 10px;
} }
@media (max-width: 820px) { .chart-wrap {
.stories-grid { background: #fbf8f3;
border: 1px solid #e4d9c5;
border-radius: 12px;
padding: 16px 18px;
}
.chart-caption {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 8px;
font-size: 0.84rem;
color: #10192b;
}
.chart-highlight {
font-size: 0.75rem;
font-weight: 800;
color: #e2c687;
background: #10192b;
padding: 3px 10px;
border-radius: 9999px;
}
.chart-svg {
width: 100%;
height: auto;
overflow: visible;
}
@media (max-width: 860px) {
.reviews-layout-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
+183 -101
View File
@@ -1,139 +1,221 @@
<template> <template>
<section class="teacher-section"> <section id="tutor-section" class="royal-teacher-section">
<div class="section-header"> <div class="teacher-inner">
<span class="section-badge">믿을 수 있는 1:1 멘토</span> <div class="section-header">
<h2 class="section-title">상위 1% 검증된 전문 강사진이 함께합니다</h2> <span class="section-badge">TUTOR LINEUP</span>
<p class="section-desc">단순 알바 대학생 과외가 아닙니다. 학력, 경력, 성향, 티칭 역량까지 4단계 철저한 검증을 통과한 전문 선생님만 배정합니다.</p> <h2 class="section-title">Tutor Lineup</h2>
</div> <p class="section-subtitle">서울대, 연세대, 고려대 석박사 및 베테랑 교사</p>
<div class="steps-grid">
<div class="step-card">
<div class="step-badge">STEP 1</div>
<h3 class="step-title">신원 & 학력 100% 검증</h3>
<p class="step-desc">재학·졸업증명서, 신분증, 성범죄 경력 조회 등 공인 서류 전수 대조</p>
</div> </div>
<div class="step-card"> <!-- 시안과 동일한 3인 정장 강사 라인업 카드 -->
<div class="step-badge">STEP 2</div> <div class="tutors-grid">
<article class="tutor-card">
<h3 class="step-title">과목별 심층 필기·시범강의</h3> <div class="tutor-img-wrap">
<p class="step-desc">기출 문제 풀이 및 20분 모의 시범 강의를 통한 개념 전달력 평가</p> <img
</div> src="/img/tutor-1.jpg"
alt="서울대·연세대·고려대 수리과학 전담 베테랑 교사"
width="600"
height="600"
loading="lazy"
decoding="async"
class="tutor-img"
/>
</div>
<div class="tutor-info">
<span class="tutor-role">수리·과학 전담</span>
<h3 class="tutor-name">서울대, 연세대, 고려대<br />석박사 및 베테랑 교사</h3>
<p class="tutor-desc">학력·경력·신원 100% 서류 검증 및 내신 킬러 문항 1:1 역추적 지도</p>
<div class="tutor-rate">
<span>성적 향상도</span>
<strong>98%</strong>
</div>
</div>
</article>
<div class="step-card"> <article class="tutor-card">
<div class="step-badge">STEP 3</div> <div class="tutor-img-wrap">
<img
<h3 class="step-title">학생 성향 매칭 인터뷰</h3> src="/img/tutor-2.jpg"
<p class="step-desc">학생의 성격(소극적/적극적), 학습 수준, 진로 목표에 최적화된 성향 분석</p> alt="서울대·연세대 영어·국어 전담 석박사 및 베테랑 교사"
</div> width="600"
height="600"
loading="lazy"
decoding="async"
class="tutor-img"
/>
</div>
<div class="tutor-info">
<span class="tutor-role">영어·국어 전담</span>
<h3 class="tutor-name">서울대, 연세대<br />석박사 및 베테랑 교사</h3>
<p class="tutor-desc">교과서·부교재 변형 예측 및 서술형 조건 감점 0점화 밀착 첨삭</p>
<div class="tutor-rate">
<span>성적 향상도</span>
<strong>98%</strong>
</div>
</div>
</article>
<div class="step-card"> <article class="tutor-card">
<div class="step-badge">STEP 4</div> <div class="tutor-img-wrap">
<img
<h3 class="step-title">무료 모의수업 & 피드백</h3> src="/img/tutor-3.jpg"
<p class="step-desc">본 수업 전 1회 무료 수업을 통해 학생과의 호흡을 최종 검증하고 시작</p> alt="서울대·연세대·고려대 입시 및 내신 1등급 베테랑 교사"
width="600"
height="600"
loading="lazy"
decoding="async"
class="tutor-img"
/>
</div>
<div class="tutor-info">
<span class="tutor-role">입시·특목고 전담</span>
<h3 class="tutor-name">서울대, 연세대, 고려대<br />베테랑 교사</h3>
<p class="tutor-desc">학생 성향 맞춤 인터뷰 배정 및 1회 무료 모의수업 후 최종 확정</p>
<div class="tutor-rate">
<span>성적 향상도</span>
<strong>98%</strong>
</div>
</div>
</article>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<style scoped> <style scoped>
.teacher-section { .royal-teacher-section {
max-width: 1080px; background: #f4ece0;
border-top: 1px solid #e4d9c5;
border-bottom: 1px solid #e4d9c5;
padding: clamp(48px, 6vw, 72px) 16px;
margin: 0 auto 50px; margin: 0 auto 50px;
padding: 0 16px; }
.teacher-inner {
max-width: 1080px;
margin: 0 auto;
} }
.section-header { .section-header {
text-align: center; text-align: center;
margin-bottom: 28px; margin-bottom: 32px;
} }
.section-badge { .section-badge {
display: inline-block; display: inline-block;
font-size: clamp(0.78rem, 2vw, 0.88rem); font-size: 0.76rem;
font-weight: 800;
letter-spacing: 0.22em;
color: #9e7a32;
margin-bottom: 6px;
}
.section-title {
font-size: clamp(1.45rem, 3.8vw, 2.1rem);
font-weight: 900;
color: #10192b;
letter-spacing: -0.5px;
}
.section-subtitle {
font-size: clamp(0.92rem, 2.2vw, 1.08rem);
font-weight: 700; font-weight: 700;
color: var(--primary-color, #1e3a8a); color: #334155;
background: var(--badge-bg, rgba(30, 58, 138, 0.08)); margin-top: 4px;
padding: 5px 12px; }
.tutors-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.tutor-card {
background: #ffffff;
border: 1.5px solid #dec492;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 24px rgba(16, 25, 43, 0.06);
display: flex;
flex-direction: column;
transition: transform 0.25s ease;
}
.tutor-card:hover {
transform: translateY(-5px);
}
.tutor-img-wrap {
width: 100%;
aspect-ratio: 1 / 1;
background: #e8dccb;
overflow: hidden;
}
.tutor-img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
}
.tutor-info {
padding: 20px 18px 22px;
text-align: center;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.tutor-role {
display: inline-block;
align-self: center;
font-size: 0.72rem;
font-weight: 800;
color: #9e7a32;
background: #fbf8f3;
border: 1px solid #e4d9c5;
padding: 3px 10px;
border-radius: 9999px; border-radius: 9999px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.section-title { .tutor-name {
font-size: clamp(1.3rem, 3.8vw, 1.95rem); font-size: 1rem;
font-weight: 800; font-weight: 800;
color: var(--text-color, #1e293b); color: #10192b;
letter-spacing: -0.6px; line-height: 1.4;
word-break: keep-all;
line-height: 1.35;
}
.section-desc {
font-size: clamp(0.85rem, 2.2vw, 0.98rem);
color: #64748b;
margin-top: 6px;
word-break: keep-all;
max-width: 680px;
margin-left: auto;
margin-right: auto;
}
.steps-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
}
.step-card {
background: var(--bg-card, #ffffff);
border: 1px solid var(--border-color, #e2e8f0);
border-radius: var(--card-radius, 14px);
padding: clamp(16px, 3vw, 22px) 14px;
text-align: center;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.04);
}
.step-badge {
display: inline-block;
font-size: 0.72rem;
font-weight: 800;
color: var(--accent-color, #d97706);
background: rgba(217, 119, 6, 0.1);
padding: 2px 8px;
border-radius: 4px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.step-icon { .tutor-desc {
font-size: 2rem; font-size: 0.8rem;
margin-bottom: 8px;
}
.step-title {
font-size: clamp(0.92rem, 2.2vw, 1.05rem);
font-weight: 800;
color: var(--text-color, #0f172a);
margin-bottom: 6px;
word-break: keep-all;
}
.step-desc {
font-size: clamp(0.75rem, 1.8vw, 0.84rem);
color: #64748b; color: #64748b;
line-height: 1.5; line-height: 1.5;
margin-bottom: 14px;
word-break: keep-all; word-break: keep-all;
} }
.tutor-rate {
border-top: 1px solid #f1eae0;
padding-top: 12px;
font-size: 0.92rem;
font-weight: 800;
color: #10192b;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.tutor-rate strong {
color: #b58e43;
font-size: 1.1rem;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.steps-grid { .tutors-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 360px) {
.steps-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
+18 -18
View File
@@ -214,7 +214,7 @@
"sigungu": "마포구", "sigungu": "마포구",
"dong": "", "dong": "",
"address": "서울특별시 마포구 성미산로 111", "address": "서울특별시 마포구 성미산로 111",
"detailAddress": ", 경성고등학교 (���남동)", "detailAddress": ", 경성고등학교 (연남동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
"foundation": "사립", "foundation": "사립",
@@ -529,7 +529,7 @@
"detailAddress": "(구로동)", "detailAddress": "(구로동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공���", "foundation": "공립",
"homepage": "http://guhyun.sen.hs.kr" "homepage": "http://guhyun.sen.hs.kr"
}, },
{ {
@@ -666,9 +666,9 @@
"code": "7010073", "code": "7010073",
"name": "당곡고등학교", "name": "당곡고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "��악구", "sigungu": "관악구",
"dong": "봉천동", "dong": "봉천동",
"address": "서울특별시 ��악구 봉천로21길 108", "address": "서울특별시 관악구 봉천로21길 108",
"detailAddress": ", 당곡고등학교 (봉천동)", "detailAddress": ", 당곡고등학교 (봉천동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -691,7 +691,7 @@
{ {
"code": "7010138", "code": "7010138",
"name": "대광고등학교", "name": "대광고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "신설동", "dong": "신설동",
"address": "서울특별시 동대문구 안암로 6", "address": "서울특별시 동대문구 안암로 6",
@@ -812,7 +812,7 @@
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 신정이펜1로 11", "address": "서울특별시 양천구 신정이펜1로 11",
"detailAddress": ", 대일관광고���학교 (신정동, 대일관광고등학교)", "detailAddress": ", 대일관광고등학교 (신정동, 대일관광고등학교)",
"type": "특성화고", "type": "특성화고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
@@ -834,7 +834,7 @@
{ {
"code": "7010146", "code": "7010146",
"name": "대진고등학교", "name": "대진고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "노원구", "sigungu": "노원구",
"dong": "하계동", "dong": "하계동",
"address": "서울특별시 노원구 공릉로62가길 47", "address": "서울특별시 노원구 공릉로62가길 47",
@@ -954,7 +954,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "금천구", "sigungu": "금천구",
"dong": "독산동", "dong": "독산동",
"address": "서울특별시 금천구 ��성로 64", "address": "서울특별시 금천구 문성로 64",
"detailAddress": ", 독산고등학교 (독산동)", "detailAddress": ", 독산고등학교 (독산동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -976,7 +976,7 @@
}, },
{ {
"code": "7010151", "code": "7010151",
"name": "동국대학교사범대학부속��등학교", "name": "동국대학교사범대학부속고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "장안동", "dong": "장안동",
@@ -1646,7 +1646,7 @@
"address": "서울특별시 동작구 대방동1길 46", "address": "서울특별시 동작구 대방동1길 46",
"detailAddress": ", 서울공업고등학교 (대방동)", "detailAddress": ", 서울공업고등학교 (대방동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공��", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "http://seoul-th.sen.hs.kr" "homepage": "http://seoul-th.sen.hs.kr"
}, },
@@ -2614,7 +2614,7 @@
}, },
{ {
"code": "7010204", "code": "7010204",
"name": "숭의여자��등학교", "name": "숭의여자고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동작구", "sigungu": "동작구",
"dong": "대방동", "dong": "대방동",
@@ -2683,7 +2683,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 안���천로 739", "address": "서울특별시 양천구 안양천로 739",
"detailAddress": "(신정동,신목고등학교)", "detailAddress": "(신정동,신목고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -2709,7 +2709,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "강북구", "sigungu": "강북구",
"dong": "미아동", "dong": "미아동",
"address": "서울��별시 강북구 솔매로49길 20", "address": "서울특별시 강북구 솔매로49길 20",
"detailAddress": "(미아동,신일중고등학교)", "detailAddress": "(미아동,신일중고등학교)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -2967,9 +2967,9 @@
"code": "7010100", "code": "7010100",
"name": "영신고등학교", "name": "영신고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "영���포구", "sigungu": "영등포구",
"dong": "신길동", "dong": "신길동",
"address": "서울 영���포구 대방천로14길 18", "address": "서울 영등포구 대방천로14길 18",
"detailAddress": ", 영신고등학교 (신길동,영신고등학교)", "detailAddress": ", 영신고등학교 (신길동,영신고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -3278,7 +3278,7 @@
{ {
"code": "7010547", "code": "7010547",
"name": "이화여자대학교사범대학부속이화금란고등학교", "name": "이화여자대학교사범대학부속이화금란고등학교",
"region": "서울특별��", "region": "서울특별시",
"sigungu": "서대문구", "sigungu": "서대문구",
"dong": "대신동", "dong": "대신동",
"address": "서울특별시 서대문구 성산로 560", "address": "서울특별시 서대문구 성산로 560",
@@ -3398,7 +3398,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "송파구", "sigungu": "송파구",
"dong": "신천동", "dong": "신천동",
"address": "서울특별시 송파구 ��림픽로33길 71", "address": "서울특별시 송파구 올림픽로33길 71",
"detailAddress": ", 잠실고등학교 (신천동)", "detailAddress": ", 잠실고등학교 (신천동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -3759,7 +3759,7 @@
{ {
"code": "7010918", "code": "7010918",
"name": "하나고등학교", "name": "하나고등학교",
"region": "��울특별시", "region": "서울특별시",
"sigungu": "은평구", "sigungu": "은평구",
"dong": "진관동", "dong": "진관동",
"address": "서울특별시 은평구 연서로 535", "address": "서울특별시 은평구 연서로 535",
+8 -8
View File
@@ -361,8 +361,8 @@
"dong": "창동" "dong": "창동"
}, },
{ {
"fullName": "서울���별시 동대문구 신설동", "fullName": "서울특별시 동대문구 신설동",
"sido": "서울���별시", "sido": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "신설동" "dong": "신설동"
}, },
@@ -505,8 +505,8 @@
"dong": "합정동" "dong": "합정동"
}, },
{ {
"fullName": "서울특별�� 서대문구 대신동", "fullName": "서울특별시 서대문구 대신동",
"sido": "서울특별��", "sido": "서울특별시",
"sigungu": "서대문구", "sigungu": "서대문구",
"dong": "대신동" "dong": "대신동"
}, },
@@ -1652,8 +1652,8 @@
], ],
"신설동": [ "신설동": [
{ {
"fullName": "서울���별시 동대문구 신설동", "fullName": "서울특별시 동대문구 신설동",
"sido": "서울���별시", "sido": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "신설동" "dong": "신설동"
} }
@@ -1844,8 +1844,8 @@
], ],
"대신동": [ "대신동": [
{ {
"fullName": "서울특별�� 서대문구 대신동", "fullName": "서울특별시 서대문구 대신동",
"sido": "서울특별��", "sido": "서울특별시",
"sigungu": "서대문구", "sigungu": "서대문구",
"dong": "대신동" "dong": "대신동"
} }
+37 -37
View File
@@ -295,7 +295,7 @@
{ {
"code": "7010146", "code": "7010146",
"name": "대진고등학교", "name": "대진고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "노원구", "sigungu": "노원구",
"dong": "하계동", "dong": "하계동",
"address": "서울특별시 노원구 공릉로62가길 47", "address": "서울특별시 노원구 공릉로62가길 47",
@@ -981,7 +981,7 @@
"detailAddress": "(구로동)", "detailAddress": "(구로동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공���", "foundation": "공립",
"homepage": "http://guhyun.sen.hs.kr" "homepage": "http://guhyun.sen.hs.kr"
} }
], ],
@@ -1003,9 +1003,9 @@
"code": "7010073", "code": "7010073",
"name": "당곡고등학교", "name": "당곡고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "��악구", "sigungu": "관악구",
"dong": "봉천동", "dong": "봉천동",
"address": "서울특별시 ��악구 봉천로21길 108", "address": "서울특별시 관악구 봉천로21길 108",
"detailAddress": ", 당곡고등학교 (봉천동)", "detailAddress": ", 당곡고등학교 (봉천동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -1192,7 +1192,7 @@
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 신정이펜1로 11", "address": "서울특별시 양천구 신정이펜1로 11",
"detailAddress": ", 대일관광고���학교 (신정동, 대일관광고등학교)", "detailAddress": ", 대일관광고등학교 (신정동, 대일관광고등학교)",
"type": "특성화고", "type": "특성화고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
@@ -1243,7 +1243,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 안���천로 739", "address": "서울특별시 양천구 안양천로 739",
"detailAddress": "(신정동,신목고등학교)", "detailAddress": "(신정동,신목고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -1499,7 +1499,7 @@
{ {
"code": "7010138", "code": "7010138",
"name": "대광고등학교", "name": "대광고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "신설동", "dong": "신설동",
"address": "서울특별시 동대문구 안암로 6", "address": "서울특별시 동대문구 안암로 6",
@@ -1651,9 +1651,9 @@
"code": "7010100", "code": "7010100",
"name": "영신고등학교", "name": "영신고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "영���포구", "sigungu": "영등포구",
"dong": "신길동", "dong": "신길동",
"address": "서울 영���포구 대방천로14길 18", "address": "서울 영등포구 대방천로14길 18",
"detailAddress": ", 영신고등학교 (신길동,영신고등학교)", "detailAddress": ", 영신고등학교 (신길동,영신고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -2035,7 +2035,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "금천구", "sigungu": "금천구",
"dong": "독산동", "dong": "독산동",
"address": "서울특별시 금천구 ��성로 64", "address": "서울특별시 금천구 문성로 64",
"detailAddress": ", 독산고등학교 (독산동)", "detailAddress": ", 독산고등학교 (독산동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -2046,7 +2046,7 @@
"장안동": [ "장안동": [
{ {
"code": "7010151", "code": "7010151",
"name": "동국대학교사범대학부속��등학교", "name": "동국대학교사범대학부속고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "장안동", "dong": "장안동",
@@ -2530,7 +2530,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "강북구", "sigungu": "강북구",
"dong": "미아동", "dong": "미아동",
"address": "서울��별시 강북구 솔매로49길 20", "address": "서울특별시 강북구 솔매로49길 20",
"detailAddress": "(미아동,신일중고등학교)", "detailAddress": "(미아동,신일중고등학교)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -2740,7 +2740,7 @@
"address": "서울특별시 동작구 대방동1길 46", "address": "서울특별시 동작구 대방동1길 46",
"detailAddress": ", 서울공업고등학교 (대방동)", "detailAddress": ", 서울공업고등학교 (대방동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공��", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "http://seoul-th.sen.hs.kr" "homepage": "http://seoul-th.sen.hs.kr"
}, },
@@ -2759,7 +2759,7 @@
}, },
{ {
"code": "7010204", "code": "7010204",
"name": "숭의여자��등학교", "name": "숭의여자고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동작구", "sigungu": "동작구",
"dong": "대방동", "dong": "대방동",
@@ -3634,7 +3634,7 @@
{ {
"code": "7010918", "code": "7010918",
"name": "하나고등학교", "name": "하나고등학교",
"region": "��울특별시", "region": "서울특별시",
"sigungu": "은평구", "sigungu": "은평구",
"dong": "진관동", "dong": "진관동",
"address": "서울특별시 은평구 연서로 535", "address": "서울특별시 은평구 연서로 535",
@@ -4006,7 +4006,7 @@
{ {
"code": "7010547", "code": "7010547",
"name": "이화여자대학교사범대학부속이화금란고등학교", "name": "이화여자대학교사범대학부속이화금란고등학교",
"region": "서울특별��", "region": "서울특별시",
"sigungu": "서대문구", "sigungu": "서대문구",
"dong": "대신동", "dong": "대신동",
"address": "서울특별시 서대문구 성산로 560", "address": "서울특별시 서대문구 성산로 560",
@@ -4054,7 +4054,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "송파구", "sigungu": "송파구",
"dong": "신천동", "dong": "신천동",
"address": "서울특별시 송파구 ��림픽로33길 71", "address": "서울특별시 송파구 올림픽로33길 71",
"detailAddress": ", 잠실고등학교 (신천동)", "detailAddress": ", 잠실고등학교 (신천동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -4517,7 +4517,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "송파구", "sigungu": "송파구",
"dong": "신천동", "dong": "신천동",
"address": "서울특별시 송파구 ��림픽로33길 71", "address": "서울특별시 송파구 올림픽로33길 71",
"detailAddress": ", 잠실고등학교 (신천동)", "detailAddress": ", 잠실고등학교 (신천동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
@@ -4607,7 +4607,7 @@
{ {
"code": "7010547", "code": "7010547",
"name": "이화여자대학교사범대학부속이화금란고등학교", "name": "이화여자대학교사범대학부속이화금란고등학교",
"region": "서울특별��", "region": "서울특별시",
"sigungu": "서대문구", "sigungu": "서대문구",
"dong": "대신동", "dong": "대신동",
"address": "서울특별시 서대문구 성산로 560", "address": "서울특별시 서대문구 성산로 560",
@@ -4914,7 +4914,7 @@
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 신정이펜1로 11", "address": "서울특별시 양천구 신정이펜1로 11",
"detailAddress": ", 대일관광고���학교 (신정동, 대일관광고등학교)", "detailAddress": ", 대일관광고등학교 (신정동, 대일관광고등학교)",
"type": "특성화고", "type": "특성화고",
"coedu": "여", "coedu": "여",
"foundation": "사립", "foundation": "사립",
@@ -4978,7 +4978,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "양천구", "sigungu": "양천구",
"dong": "신정동", "dong": "신정동",
"address": "서울특별시 양천구 안���천로 739", "address": "서울특별시 양천구 안양천로 739",
"detailAddress": "(신정동,신목고등학교)", "detailAddress": "(신정동,신목고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -5488,7 +5488,7 @@
{ {
"code": "7010146", "code": "7010146",
"name": "대진고등학교", "name": "대진고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "노원구", "sigungu": "노원구",
"dong": "하계동", "dong": "하계동",
"address": "서울특별시 노원구 공릉로62가길 47", "address": "서울특별시 노원구 공릉로62가길 47",
@@ -6163,7 +6163,7 @@
"address": "서울특별시 동작구 대방동1길 46", "address": "서울특별시 동작구 대방동1길 46",
"detailAddress": ", 서울공업고등학교 (대방동)", "detailAddress": ", 서울공업고등학교 (대방동)",
"type": "특성화고", "type": "특성화고",
"coedu": "남여공��", "coedu": "남여공학",
"foundation": "공립", "foundation": "공립",
"homepage": "http://seoul-th.sen.hs.kr" "homepage": "http://seoul-th.sen.hs.kr"
}, },
@@ -6195,7 +6195,7 @@
}, },
{ {
"code": "7010204", "code": "7010204",
"name": "숭의여자��등학교", "name": "숭의여자고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동작구", "sigungu": "동작구",
"dong": "대방동", "dong": "대방동",
@@ -6542,7 +6542,7 @@
"sigungu": "마포구", "sigungu": "마포구",
"dong": "", "dong": "",
"address": "서울특별시 마포구 성미산로 111", "address": "서울특별시 마포구 성미산로 111",
"detailAddress": ", 경성고등학교 (���남동)", "detailAddress": ", 경성고등학교 (연남동)",
"type": "일반고", "type": "일반고",
"coedu": "남", "coedu": "남",
"foundation": "사립", "foundation": "사립",
@@ -6716,7 +6716,7 @@
"detailAddress": "(구로동)", "detailAddress": "(구로동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
"foundation": "공���", "foundation": "공립",
"homepage": "http://guhyun.sen.hs.kr" "homepage": "http://guhyun.sen.hs.kr"
}, },
{ {
@@ -6960,7 +6960,7 @@
{ {
"code": "7010138", "code": "7010138",
"name": "대광고등학교", "name": "대광고등학교",
"region": "서울���별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "신설동", "dong": "신설동",
"address": "서울특별시 동대문구 안암로 6", "address": "서울특별시 동대문구 안암로 6",
@@ -6972,7 +6972,7 @@
}, },
{ {
"code": "7010151", "code": "7010151",
"name": "동국대학교사범대학부속��등학교", "name": "동국대학교사범대학부속고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "동대문구", "sigungu": "동대문구",
"dong": "장안동", "dong": "장안동",
@@ -7424,7 +7424,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "금천구", "sigungu": "금천구",
"dong": "독산동", "dong": "독산동",
"address": "서울특별시 금천구 ��성로 64", "address": "서울특별시 금천구 문성로 64",
"detailAddress": ", 독산고등학교 (독산동)", "detailAddress": ", 독산고등학교 (독산동)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -7590,14 +7590,14 @@
"homepage": "http://hyomun.sen.hs.kr" "homepage": "http://hyomun.sen.hs.kr"
} }
], ],
"��악구": [ "관악구": [
{ {
"code": "7010073", "code": "7010073",
"name": "당곡고등학교", "name": "당곡고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "��악구", "sigungu": "관악구",
"dong": "봉천동", "dong": "봉천동",
"address": "서울특별시 ��악구 봉천로21길 108", "address": "서울특별시 관악구 봉천로21길 108",
"detailAddress": ", 당곡고등학교 (봉천동)", "detailAddress": ", 당곡고등학교 (봉천동)",
"type": "자율고", "type": "자율고",
"coedu": "남여공학", "coedu": "남여공학",
@@ -7975,7 +7975,7 @@
{ {
"code": "7010918", "code": "7010918",
"name": "하나고등학교", "name": "하나고등학교",
"region": "��울특별시", "region": "서울특별시",
"sigungu": "은평구", "sigungu": "은평구",
"dong": "진관동", "dong": "진관동",
"address": "서울특별시 은평구 연서로 535", "address": "서울특별시 은평구 연서로 535",
@@ -8454,7 +8454,7 @@
"region": "서울특별시", "region": "서울특별시",
"sigungu": "강북구", "sigungu": "강북구",
"dong": "미아동", "dong": "미아동",
"address": "서울��별시 강북구 솔매로49길 20", "address": "서울특별시 강북구 솔매로49길 20",
"detailAddress": "(미아동,신일중고등학교)", "detailAddress": "(미아동,신일중고등학교)",
"type": "자율고", "type": "자율고",
"coedu": "남", "coedu": "남",
@@ -8501,14 +8501,14 @@
"homepage": "http://hyehwa-h.sen.hs.kr" "homepage": "http://hyehwa-h.sen.hs.kr"
} }
], ],
"영���포구": [ "영등포구": [
{ {
"code": "7010100", "code": "7010100",
"name": "영신고등학교", "name": "영신고등학교",
"region": "서울특별시", "region": "서울특별시",
"sigungu": "영���포구", "sigungu": "영등포구",
"dong": "신길동", "dong": "신길동",
"address": "서울 영���포구 대방천로14길 18", "address": "서울 영등포구 대방천로14길 18",
"detailAddress": ", 영신고등학교 (신길동,영신고등학교)", "detailAddress": ", 영신고등학교 (신길동,영신고등학교)",
"type": "일반고", "type": "일반고",
"coedu": "남여공학", "coedu": "남여공학",
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응 // SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true, ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화 // Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: { nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' }, { name: 'format-detection', content: 'telephone=no' }
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || '299523ed34c7419d7fb724426e14b5def8badac7' }
] ]
} }
} }
+15 -15
View File
@@ -1,24 +1,24 @@
<template> <template>
<div class="landing-page-wrap bg-[#F8FAFC] min-h-screen text-slate-800 antialiased"> <div class="landing-page-wrap bg-[#fbf8f3] min-h-screen text-[#151c28] antialiased">
<!-- 1. 클린 미니멀 프리미엄 네비게이션 헤더 (기본 홈과 동일) --> <!-- 1. 로열클래스 다크 네이비 & 골드 엠블럼 헤더 -->
<AppHeader :siteName="siteName" /> <AppHeader :siteName="siteName" consultHref="#consult-section" />
<!-- 2. 더세이브 방식 시맨틱 브레드크럼 및 지역 센터 식별 서브 바 --> <!-- 2. 더세이브 방식 시맨틱 브레드크럼 및 지역 센터 식별 서브 바 (로열 네이비·골드 톤) -->
<nav class="bc bg-blue-50/70 border-b border-blue-100/70 py-2.5 px-4" aria-label="현재 위치"> <nav class="bc bg-[#131e35] border-b border-[#c9a55c]/30 py-2.5 px-4" aria-label="현재 위치">
<div class="max-w-4xl mx-auto flex items-center justify-between flex-wrap gap-2 text-xs"> <div class="max-w-4xl mx-auto flex items-center justify-between flex-wrap gap-2 text-xs">
<div class="flex items-center gap-1.5 flex-wrap text-slate-600"> <div class="flex items-center gap-1.5 flex-wrap text-slate-300">
<NuxtLink to="/" class="font-semibold text-blue-700 hover:underline">홈</NuxtLink> <NuxtLink to="/" class="font-semibold text-[#e2c687] hover:underline">홈</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-500">›</span>
<NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="hover:underline">{{ parsed.dong }} 과외</NuxtLink> <NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="text-slate-200 hover:text-[#e2c687] hover:underline">{{ parsed.dong }} 과외</NuxtLink>
<span class="text-slate-400">›</span> <span class="text-slate-500">›</span>
<strong class="font-bold text-slate-900">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong> <strong class="font-bold text-white">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong>
</div> </div>
<div class="hidden sm:flex items-center gap-1.5 text-slate-500 font-medium"> <div class="hidden sm:flex items-center gap-1.5 text-slate-300 font-medium">
<span class="inline-flex items-center gap-1 font-bold text-blue-700 bg-white px-2.5 py-0.5 rounded-full border border-blue-200/80 shadow-xs"> <span class="inline-flex items-center gap-1 font-bold text-[#0d1424] bg-[#e2c687] px-2.5 py-0.5 rounded-full border border-[#f2dfb3] shadow-xs">
<i class="fa-solid fa-location-dot text-blue-600 text-[10px]"></i> <i class="fa-solid fa-crown text-[#0d1424] text-[10px]"></i>
{{ parsed.shortRegionName }} 센터 {{ parsed.shortRegionName }} 센터
</span> </span>
<span>검증된 전문 선생님 100% 매칭</span> <span class="text-[#ebd5a4]">검증된 전문 선생님 100% 매칭</span>
</div> </div>
</div> </div>
</nav> </nav>
+882 -388
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More