방식 개편

This commit is contained in:
2026-11-05 14:07:55 +09:00
parent 6d78eccb65
commit 67a7fa0326
156 changed files with 107591 additions and 12747 deletions
+96 -110
View File
@@ -17,8 +17,8 @@
</p>
</div>
<!-- 2 - 2 - 1 그리드 정렬 레이아웃 -->
<div class="subject-grid-221">
<!-- 가로 1열 5칸 캐러셀 레이아웃 -->
<div class="subject-grid-carousel">
<div
v-for="(item, idx) in subjectList"
:key="item.name"
@@ -26,17 +26,18 @@
:class="`card-item-${idx + 1}`"
@click="openModal(item)"
>
<div class="card-inner">
<figure class="card-inner m-0">
<div class="img-wrap">
<img
:src="item.image"
:alt="`${item.name} 1:1 맞춤 지도`"
width="600"
height="600"
:src="item.path"
:alt="`${shortSido || defaultSido} ${gradeShort || computedGradeShort} ${item.keyName} 과외 · ${regionDong || defaultDong} ${item.name}`"
width="1080"
height="1080"
decoding="async"
loading="eager"
fetchpriority="high"
class="subject-img"
loading="lazy"
/>
<span class="subject-tag">{{ item.tag }}</span>
<div class="zoom-overlay">
<span class="zoom-btn">
<i class="fa-solid fa-magnifying-glass-plus"></i>
@@ -45,18 +46,18 @@
</div>
</div>
<div class="card-info">
<figcaption class="card-info">
<div class="card-subject-name">
<span class="sub-name">{{ item.name }}</span>
<h3 class="sub-name m-0 text-inherit font-extrabold">{{ shortSido || defaultSido }} {{ gradeShort || computedGradeShort }} {{ item.name }}</h3>
<span class="sub-grade">{{ grade }}</span>
</div>
<p class="card-subject-desc">{{ item.desc }}</p>
<div class="card-action">
<NuxtLink :to="getSubjectPageUrl(item.subjectKey)" class="card-action" @click.stop>
<span>자세히 보기 & 신청</span>
<i class="fa-solid fa-arrow-right"></i>
</div>
</div>
</div>
</NuxtLink>
</figcaption>
</figure>
</div>
</div>
@@ -87,21 +88,21 @@
<!-- 모달 좌측: 고해상도 생성 이미지 원본 -->
<div class="modal-img-col">
<img
:src="selectedItem.image"
:alt="`${selectedItem.name} 1:1 맞춤 과외`"
width="600"
height="600"
:src="selectedItem.path"
:alt="`${shortSido || defaultSido} ${gradeShort || computedGradeShort} ${selectedItem.keyName} 과외 · ${regionDong || defaultDong} ${selectedItem.name}`"
width="1080"
height="1080"
decoding="async"
class="modal-main-img"
/>
<span class="modal-img-badge">{{ selectedItem.tag }}</span>
</div>
<!-- 모달 우측: 상세 설명 및 액션 버튼 -->
<div class="modal-content-col">
<div class="modal-header">
<span class="modal-pill">{{ regionDong || '관내' }} 1:1 밀착 코칭</span>
<span class="modal-pill">{{ regionDong || defaultDong }} 1:1 밀착 코칭</span>
<h3 class="modal-title">
{{ selectedItem.name }}
{{ shortSido || defaultSido }} {{ gradeShort || computedGradeShort }} {{ selectedItem.name }}
<span class="text-sub">1:1 맞춤 마스터 클래스</span>
</h3>
<p class="modal-slogan">{{ selectedItem.slogan }}</p>
@@ -148,7 +149,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
@@ -158,7 +159,15 @@ const props = defineProps({
},
grade: {
type: String,
default: '중·고등'
default: '중학생'
},
gradeShort: {
type: String,
default: '중등'
},
shortSido: {
type: String,
default: '부산'
},
currentSubject: {
type: String,
@@ -169,16 +178,22 @@ const props = defineProps({
const router = useRouter()
const config = useRuntimeConfig()
const siteDomain = config.public?.siteDomain || 'https://aeseo.cloud'
const siteDomain = config.public?.siteDomain || 'https://soohyun.cloud'
const defaultDong = '우동'
const defaultSido = '부산'
// 5대 핵심 과목 (2 2 1 순서 배치)
const computedGradeShort = computed(() => {
return props.grade === '초등학생' ? '초등' : props.grade === '고등학생' ? '고등' : '중등'
})
// 5대 핵심 과목 (가로 1열 5칸 캐러셀 배치)
const subjectList = [
// 1행: 수학, 영어
{
name: '수학과외',
keyName: '수학',
subjectKey: 'math',
tag: '인기 1위',
image: `${siteDomain}/img/math-tutoring.png`,
image: '/img/math-tutoring.png',
path: '/img/math-tutoring.png',
desc: '개념 구멍 추적 및 킬러 문항 1:1 완파',
slogan: '단순 공식 암기가 아닌 원리 이해와 킬러 문항 해결력을 기릅니다.',
@@ -190,9 +205,10 @@ const subjectList = [
},
{
name: '영어과외',
keyName: '영어',
subjectKey: 'english',
tag: '내신 필수',
image: `${siteDomain}/img/english-tutoring.png`,
image: '/img/english-tutoring.png',
path: '/img/english-tutoring.png',
desc: '교과서·부교재 전 지문 변형 및 서술형 영작',
slogan: '어휘, 문법, 서술형 감점 0점화로 내신 1등급을 완성합니다.',
@@ -202,12 +218,12 @@ const subjectList = [
{ title: '1:1 구문 독해 & 빈칸 추론 특훈', desc: '문장 구조 분석을 통해 킬러 빈칸·순서 배열 문제를 빠르고 정확히 풉니다.' }
]
},
// 2행: 국어, 과학
{
name: '국어과외',
keyName: '국어',
subjectKey: 'korean',
tag: '문해력 완성',
image: `${siteDomain}/img/korean-tutoring.png`,
image: '/img/korean-tutoring.png',
path: '/img/korean-tutoring.png',
desc: '비문학 독해 논리와 교과서 문학 1:1 정밀 분석',
slogan: '긴 지문도 흔들리지 않는 문해력과 서술형 채점 기준을 공략합니다.',
@@ -219,9 +235,10 @@ const subjectList = [
},
{
name: '과학과외',
keyName: '과학',
subjectKey: 'science',
tag: '통합과학',
image: `${siteDomain}/img/science-tutoring.png`,
image: '/img/science-tutoring.png',
path: '/img/science-tutoring.png',
desc: '통합과학 원리 이해 및 물화생지 킬러 단원 정복',
slogan: '까다로운 계산과 복잡한 개념을 1:1 시각화와 원리 풀이로 해결합니다.',
@@ -231,12 +248,12 @@ const subjectList = [
{ title: '수행평가 실험 보고서 1:1 가이드', desc: '지필고사뿐 아니라 감점되기 쉬운 과학 탐구 보고서 만점을 케어합니다.' }
]
},
// 3행: 사회 (1개 단독 센터 정렬)
{
name: '사회과외',
keyName: '사회',
subjectKey: 'social',
tag: '통합사회',
image: `${siteDomain}/img/social-studies-tutoring.png`,
image: '/img/social-studies-tutoring.png',
path: '/img/social-studies-tutoring.png',
desc: '통합사회·한국사 핵심 흐름 및 사탐 만점 전략',
slogan: '무작정 외우지 않고 역사와 사회 현상의 맥락을 이해하며 1등급을 쟁취합니다.',
@@ -271,11 +288,9 @@ const applyWithSubject = (subjectName) => {
const cleanSubj = subjectName.replace(/과외$/, '').trim()
if (typeof window !== 'undefined') {
// 1. 커스텀 이벤트 발송 (Vue 반응형 formData.subject 즉시 업데이트)
window.dispatchEvent(new CustomEvent('select-subject', { detail: { subject: cleanSubj } }))
setTimeout(() => {
// 2. 칩 버튼 DOM 직접 클릭 트리거 (완벽 호환)
const buttons = document.querySelectorAll('#leadFormSection button')
for (const btn of buttons) {
const text = btn.innerText.trim()
@@ -286,14 +301,12 @@ const applyWithSubject = (subjectName) => {
}
}
// 3. input/select fallback
const subjSelect = document.getElementById('weakSubject') || document.getElementById('c-subject')
if (subjSelect) {
subjSelect.value = cleanSubj
subjSelect.dispatchEvent(new Event('change'))
}
// 4. 상단 폼 위치로 부드럽게 스크롤 이동
const formTarget = document.getElementById('leadFormSection') || document.getElementById('consult-section')
if (formTarget) {
formTarget.scrollIntoView({ behavior: 'smooth' })
@@ -304,8 +317,9 @@ const applyWithSubject = (subjectName) => {
// 해당 과목 전용 페이지 URL 생성 (NuxtLink 바인딩용)
const getSubjectPageUrl = (subjectKey) => {
const dong = props.regionDong || '서울'
const grade = props.grade || '중학생'
const dong = props.regionDong || defaultDong
const validGrades = ['초등학생', '중학생', '고등학생']
const grade = validGrades.includes(props.grade) ? props.grade : '중학생'
const subMap = {
math: '수학',
english: '영어',
@@ -317,13 +331,11 @@ const getSubjectPageUrl = (subjectKey) => {
return `/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(subName)}`
}
// 해당 과목 전용 페이지로 이동
const goToSubjectPage = (subjectKey) => {
closeModal()
router.push(getSubjectPageUrl(subjectKey))
}
// ESC 키로 모달 닫기
const handleKeyDown = (e) => {
if (e.key === 'Escape' && selectedItem.value) {
closeModal()
@@ -347,7 +359,7 @@ onUnmounted(() => {
<style scoped>
.subject-gallery-section {
width: 100%;
max-width: 1080px;
max-width: 1120px;
margin: 40px auto 60px;
padding: 0 16px;
position: relative;
@@ -358,13 +370,13 @@ onUnmounted(() => {
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
border-radius: 28px;
padding: clamp(24px, 4vw, 44px) clamp(16px, 3vw, 36px);
padding: clamp(24px, 4vw, 40px) clamp(16px, 3vw, 28px);
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
}
.gallery-header {
text-align: center;
margin-bottom: 32px;
margin-bottom: 28px;
}
.gallery-badge {
@@ -398,31 +410,23 @@ onUnmounted(() => {
word-break: keep-all;
}
/* 2 - 2 - 1 그리드 레이아웃 */
.subject-grid-221 {
/* 가로 1열 5칸 캐러셀 그리드 레이아웃 */
.subject-grid-carousel {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
grid-template-columns: repeat(5, minmax(180px, 1fr));
gap: 14px;
width: 100%;
}
/* 5번째 아이템: 3행 단독 1개 (2열 span 및 중앙 정렬) */
.card-item-5 {
grid-column: 1 / -1;
justify-self: center;
width: 100%;
max-width: calc(50% - 10px);
}
.gallery-card {
cursor: pointer;
border-radius: 20px;
border-radius: 18px;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.card-inner {
background: #ffffff;
border-radius: 20px;
border-radius: 18px;
overflow: hidden;
border: 1px solid #e2e8f0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
@@ -444,6 +448,7 @@ onUnmounted(() => {
aspect-ratio: 1 / 1;
overflow: hidden;
background: #f1f5f9;
margin: 0;
}
.subject-img {
@@ -457,20 +462,6 @@ onUnmounted(() => {
transform: scale(1.05);
}
.subject-tag {
position: absolute;
top: 14px;
left: 14px;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(4px);
color: #ffffff;
font-size: 0.75rem;
font-weight: 800;
padding: 4px 12px;
border-radius: 9999px;
z-index: 2;
}
.zoom-overlay {
position: absolute;
inset: 0;
@@ -490,9 +481,9 @@ onUnmounted(() => {
.zoom-btn {
background: #ffffff;
color: #0f172a;
font-size: 0.88rem;
font-size: 0.8rem;
font-weight: 800;
padding: 10px 18px;
padding: 8px 14px;
border-radius: 9999px;
display: inline-flex;
align-items: center;
@@ -507,7 +498,7 @@ onUnmounted(() => {
}
.card-info {
padding: 18px 20px;
padding: 14px 14px 16px;
display: flex;
flex-direction: column;
flex: 1;
@@ -516,59 +507,67 @@ onUnmounted(() => {
.card-subject-name {
display: flex;
align-items: baseline;
gap: 8px;
justify-content: space-between;
gap: 6px;
margin-bottom: 6px;
}
.sub-name {
font-size: 1.2rem;
font-size: 0.95rem;
font-weight: 800;
color: #0f172a;
margin: 0;
line-height: 1.3;
}
.sub-grade {
font-size: 0.78rem;
font-size: 0.7rem;
font-weight: 700;
color: #64748b;
background: #f1f5f9;
padding: 2px 8px;
padding: 2px 6px;
border-radius: 6px;
flex-shrink: 0;
}
.card-subject-desc {
font-size: 0.85rem;
font-size: 0.78rem;
color: #475569;
line-height: 1.45;
margin-bottom: 14px;
margin-bottom: 12px;
flex: 1;
word-break: keep-all;
}
.card-action {
font-size: 0.82rem;
font-size: 0.76rem;
font-weight: 700;
color: var(--primary-color, #1e3a8a);
display: flex;
align-items: center;
gap: 6px;
gap: 5px;
transition: gap 0.2s ease;
}
.gallery-card:hover .card-action {
gap: 10px;
gap: 8px;
}
/* 모바일 반응형 대응 */
@media (max-width: 640px) {
.subject-grid-221 {
grid-template-columns: 1fr;
gap: 16px;
/* 태블릿/모바일 가로 스크롤 캐러셀 반응형 대응 */
@media (max-width: 1024px) {
.subject-grid-carousel {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 12px;
padding-bottom: 12px;
-webkit-overflow-scrolling: touch;
}
.card-item-5 {
grid-column: auto;
max-width: 100%;
justify-self: stretch;
.gallery-card {
flex: 0 0 220px;
scroll-snap-align: start;
}
}
@@ -649,19 +648,6 @@ onUnmounted(() => {
display: block;
}
.modal-img-badge {
position: absolute;
top: 16px;
left: 16px;
background: var(--accent-color, #d97706);
color: #ffffff;
font-size: 0.75rem;
font-weight: 800;
padding: 5px 12px;
border-radius: 9999px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.modal-content-col {
padding: clamp(24px, 4vw, 36px);
display: flex;
@@ -809,11 +795,11 @@ onUnmounted(() => {
.modal-fade-enter-active .modal-card,
.modal-fade-leave-active .modal-card {
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.modal-fade-enter-from .modal-card,
.modal-fade-leave-to .modal-card {
transform: scale(0.92) translateY(20px);
transform: scale(0.95) translateY(10px);
}
</style>