diff --git a/config/web.php b/config/web.php new file mode 100644 index 0000000..55cd983 --- /dev/null +++ b/config/web.php @@ -0,0 +1,49 @@ + [ + [ + 'kicker' => 'PIN FOR YOU', + 'title' => '안전하고 빠른 상품권 거래', + 'desc' => '구글플레이·문화상품권·편의점 등 인기 상품을 할인 구매하세요.', + 'cta_label' => '상품 보러가기', + 'cta_url' => '/shop', + ], + [ + 'kicker' => 'EVENT', + 'title' => '카드/휴대폰 결제 지원', + 'desc' => '원하는 결제수단으로 편하게, 발송은 빠르게.', + 'cta_label' => '결제 안내', + 'cta_url' => '/guide', + ], + [ + 'kicker' => 'SUPPORT', + 'title' => '문제 생기면 1:1 문의', + 'desc' => '빠른 응답으로 도와드릴게요.', + 'cta_label' => '1:1 문의하기', + 'cta_url' => '/cs/qna', + ], + ], + + 'cs_tabs' => [ + ['label' => '공지사항', 'route' => 'web.cs.notice.index', 'key' => 'notice'], + ['label' => '자주 묻는 질문', 'route' => 'web.cs.faq.index', 'key' => 'faq'], + ['label' => '카카오톡상담', 'route' => 'web.cs.kakao.index', 'key' => 'kakao'], + ['label' => '1:1 문의', 'route' => 'web.cs.qna.index', 'key' => 'qna'], + ['label' => '이용안내', 'route' => 'web.cs.guide.index', 'key' => 'guide'], + ], + + 'mypage_tabs' => [ + ['label' => '나의정보', 'route' => 'web.mypage.info.index', 'key' => 'info'], + ['label' => '이용내역', 'route' => 'web.mypage.usage.index', 'key' => 'usage'], + ['label' => '교환내역', 'route' => 'web.mypage.exchange.index', 'key' => 'exchange'], + ['label' => '1:1문의내역', 'route' => 'web.mypage.qna.index', 'key' => 'qna'], + ], + + 'policy_tabs' => [ + ['label' => '개인정보처리방침', 'route' => 'web.policy.privacy.index', 'key' => 'privacy'], + ['label' => '이용약관', 'route' => 'web.policy.terms.index', 'key' => 'terms'], + ['label' => '이메일무단수집거부', 'route' => 'web.policy.email.index', 'key' => 'email'], + ], + +]; diff --git a/resources/css/web.css b/resources/css/web.css index 8c778da..020ef59 100644 --- a/resources/css/web.css +++ b/resources/css/web.css @@ -1212,3 +1212,541 @@ h1, h2, h3, h4, h5, h6 { .notice-link{ grid-template-columns: 44px 1fr; } .notice-date{ display:none; } } +/* ===== Subpage Skeleton ===== */ +.subpage-wrap{ padding: 18px 0 26px; } + +.subpage-header{ + display:flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin: 40px 0 16px; + padding-left:8px; +} +.subpage-title{ + font-size: 20px; + font-weight: 900; + letter-spacing: -0.3px; + color:#0F172A; +} +.subpage-desc{ + font-size: 13px; + color:#64748B; + white-space: nowrap; + overflow:hidden; + text-overflow: ellipsis; + max-width: 55%; +} + +.breadcrumb{ margin-top: 6px; } +.breadcrumb-list{ + display:flex; + gap: 8px; + flex-wrap: wrap; + font-size: 12px; + color:#94A3B8; +} +.breadcrumb-item::after{ + content:"/"; + margin-left: 8px; + color:#CBD5E1; +} +.breadcrumb-item:last-child::after{ content:none; } +.breadcrumb-link{ color:#64748B; } +.breadcrumb-link:hover{ color:#2563EB; } +.breadcrumb-current{ color:#475569; font-weight:700; } + +.subpage-grid{ + display:grid; + grid-template-columns: 260px 1fr; + gap: 14px; + align-items: start; +} + +.subpage-tabs{ display:none; } + +.subpage-side{ position: sticky; top: 90px; } + +.content-card{ + background:#FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 16px; + padding: 16px; + box-shadow: 0 1px 2px rgba(15,23,42,.04); +} + +/* side nav */ +.subnav--side .subnav-list{ + background:#FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 16px; + padding: 10px; + display:grid; + gap: 6px; +} +.subnav-link{ + display:flex; + align-items:center; + height: 40px; + padding: 0 12px; + border-radius: 12px; + font-size: 13px; + font-weight: 800; + color:#334155; +} +.subnav-link:hover{ background:#F8FAFC; } +.subnav-link.is-active{ + background:#EFF6FF; + border: 1px solid #BFDBFE; + color:#1D4ED8; +} + +/* mobile tabs */ +.subnav--tabs{ + display:flex; + gap: 8px; + overflow-x: auto; + scrollbar-width: none; + padding-bottom: 6px; +} +.subnav--tabs::-webkit-scrollbar{ display:none; } + +.subnav-tab{ + flex: 0 0 auto; + height: 36px; + padding: 0 14px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background:#FFFFFF; + font-size: 12.5px; + font-weight: 900; + color:#334155; +} +.subnav-tab:hover{ background:#F8FAFC; } +.subnav-tab.is-active{ + background:#EFF6FF; + border-color:#BFDBFE; + color:#1D4ED8; +} + +/* notice sample page */ +.list-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom: 10px; } +.list-title{ font-size: 14px; font-weight: 900; color:#0F172A; margin:0; } +.list-desc{ font-size: 12.5px; color:#64748B; margin-top: 4px; } +.list-head{ + display:flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-bottom: 10px; +} + +.list-title{ + margin: 0; +} + +.list-desc{ + margin: 0; + text-align: right; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 60%; + font-size: 12.5px; color:#64748B; +} + +@media (max-width: 560px){ + .list-desc{ display:none; } /* 모바일은 깔끔하게 */ +} + + +.notice-list2{ display:grid; } +.notice-list2 li + li{ border-top: 1px solid #EEF2F7; } +.notice-list2 a{ + display:grid; + grid-template-columns: 54px 1fr auto; + gap: 10px; + align-items:center; + padding: 12px 6px; + border-radius: 12px; +} +.notice-list2 a:hover{ background:#F8FAFC; } + +.n2-tag{ + height: 20px; + display:inline-flex; + align-items:center; + justify-content:center; + padding: 0 10px; + border-radius: 9999px; + font-size: 11px; + font-weight: 900; + background:#EFF6FF; + border:1px solid #BFDBFE; + color:#1D4ED8; +} +.n2-tag--info{ + background:#F1F5F9; + border-color:#E2E8F0; + color:#334155; +} +.n2-text{ + font-size: 13px; + font-weight: 800; + color:#334155; + white-space: nowrap; + overflow:hidden; + text-overflow: ellipsis; +} +.n2-date{ font-size: 12px; color:#94A3B8; white-space: nowrap; } + +.n2s-text{ + font-size: 13px; + font-weight: 600; + color: #2a3647; + white-space: nowrap; + overflow:hidden; + text-overflow: ellipsis; +} + +@media (max-width: 1024px){ + .subpage-grid{ grid-template-columns: 1fr; } + .subpage-side{ display:none; } + .subpage-tabs{ display:block; } + .subpage-desc{ max-width: 100%; } +} +@media (max-width: 560px){ + .notice-list2 a{ grid-template-columns: 54px 1fr; } + .n2-date{ display:none; } +} + +/* ===== Sub Hero (compact rolling banner) ===== */ +.subhero{ + margin: 8px 0 10px; +} +.subhero-inner{ + position: relative; + overflow: hidden; + border-radius: 16px; + border: 1px solid #E5E7EB; + background: #F8FAFC; + height: 160px; /* ✅ 메인보다 낮게 */ +} +.subhero-track{ + display:flex; + height: 100%; + transition: transform .45s cubic-bezier(.25,1,.5,1); +} +.subhero-slide{ + min-width: 100%; + height: 100%; + background-image: var(--bg); + background-size: cover; + background-position: center; + position: relative; +} +.subhero-slide::after{ + content:""; + position:absolute; inset:0; + background: rgba(255,255,255,.72); /* ✅ 과하지 않게 텍스트 가독 */ +} +.subhero-content{ + position: relative; + z-index: 1; + height: 100%; + padding: 18px 18px; + display:flex; + flex-direction: column; + justify-content: center; + max-width: 720px; +} +.subhero-kicker{ + font-size: 11px; + font-weight: 900; + letter-spacing: .8px; + color: #2563EB; +} +.subhero-title{ + margin-top: 6px; + font-size: 20px; + font-weight: 900; + color:#0F172A; + letter-spacing: -0.3px; +} +.subhero-desc{ + margin-top: 6px; + font-size: 13px; + color:#64748B; +} +.subhero-cta{ + margin-top: 10px; + display:inline-flex; + align-items:center; + height: 34px; + padding: 0 12px; + border-radius: 9999px; + border: 1px solid #BFDBFE; + background: #EFF6FF; + color:#1D4ED8; + font-size: 12.5px; + font-weight: 900; + width: fit-content; +} +.subhero-cta:hover{ background:#2563EB; border-color:#2563EB; color:#fff; } + +.subhero-arrow{ + position:absolute; top:50%; + transform: translateY(-50%); + width: 34px; height: 34px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background: rgba(255,255,255,.9); + font-size: 20px; + font-weight: 900; + color:#0F172A; +} +.subhero-arrow.prev{ left: 10px; } +.subhero-arrow.next{ right: 10px; } + +.subhero-dots{ + position:absolute; bottom: 10px; left: 50%; + transform: translateX(-50%); + display:flex; gap: 6px; +} +.subhero-dot{ + width: 7px; height: 7px; + border-radius: 9999px; + background: rgba(15,23,42,.18); +} +.subhero-dot.active{ background:#2563EB; transform: scale(1.2); } +/* ===== Notice toolbar (no duplicate nav) ===== */ +.notice-toolbar{ + display:flex; + align-items:flex-start; + justify-content:space-between; + gap: 12px; + margin: 6px 0 14px; + padding: 12px 12px; + border: 1px solid #E5E7EB; + border-radius: 16px; + background: #FFFFFF; + box-shadow: 0 1px 2px rgba(15,23,42,.04); +} + +.nt-left{ + min-width: 0; + display:flex; + flex-direction:column; + gap: 8px; +} + +.nt-pill{ + width: fit-content; + height: 22px; + padding: 0 10px; + border-radius: 9999px; + background: #EFF6FF; + border: 1px solid #BFDBFE; + color: #1D4ED8; + font-size: 11px; + font-weight: 900; +} + +.nt-meta{ + font-size: 12.5px; + color: #64748B; + line-height: 1.45; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 520px; +} + +.nt-right{ + display:flex; + flex-direction:column; + gap: 10px; + align-items:flex-end; +} + +.nt-search{ + display:flex; + align-items:center; + gap: 8px; +} + +.nt-search input{ + width: 280px; + height: 38px; + padding: 0 12px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background: #F3F4F6; + font-size: 13px; +} +.nt-search input:focus{ + outline: none; + border-color: #2563EB; + background: #FFFFFF; + box-shadow: 0 0 0 2px #EFF6FF; +} + +.nt-search button{ + height: 38px; + padding: 0 12px; + border-radius: 9999px; + background: #2563EB; + color:#fff; + font-size: 13px; + font-weight: 900; + border: 1px solid #2563EB; +} + +.nt-filters{ + display:flex; + gap: 8px; + flex-wrap: wrap; + justify-content:flex-end; +} + +.nt-chip{ + height: 32px; + padding: 0 12px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background: #FFFFFF; + color: #334155; + font-size: 12.5px; + font-weight: 900; +} +.nt-chip:hover{ background:#F8FAFC; } + +.nt-chip.is-active{ + background:#EFF6FF; + border-color:#BFDBFE; + color:#1D4ED8; +} + +@media (max-width: 1024px){ + .notice-toolbar{ flex-direction:column; align-items:stretch; } + .nt-meta{ max-width: 100%; white-space: normal; overflow: visible; text-overflow: clip; } + .nt-right{ align-items:stretch; } + .nt-search input{ width: 100%; } + .nt-filters{ justify-content:flex-start; } +} + +/* ===== Hero variants ===== */ +.hero-slider{ position: relative; overflow: hidden; background: var(--color-bg-section); } +.hero-slider--main{ height: 400px; } +.hero-slider--compact{ height: 170px; border-radius: 16px; border: 1px solid #E5E7EB; margin: 14px 0 18px; } + +/* track/slide 동일 */ +.hero-track{ display:flex; height:100%; transition: transform 0.5s cubic-bezier(0.25,1,0.5,1); } +.hero-slide{ min-width:100%; height:100%; display:flex; align-items:center; justify-content:center; padding: 0 24px; } + +.hero-content{ max-width: var(--container-width); width:100%; text-align:left; padding: 0 8px; } + +/* ✅ compact에서 타이포/여백만 줄이기 */ +.hero-slider--compact .hero-slide{ padding: 0 18px; } +.hero-slider--compact .hero-title{ font-size: 22px; margin-bottom: 6px; } +.hero-slider--compact .hero-desc{ font-size: 13px; margin-bottom: 10px; } +.hero-slider--compact .btn.hero-cta{ padding: 8px 14px; font-size: 13px; } + +/* ✅ 왼쪽/오른쪽 화살표 버튼이 컨텐츠를 가리지 않게 여백 확보 */ +.slider-arrow{ + position:absolute; top:50%; transform: translateY(-50%); + width: 34px; height: 34px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background: rgba(255,255,255,.9); + font-size: 20px; font-weight: 900; + display:flex; align-items:center; justify-content:center; + z-index: 5; +} +.slider-arrow.prev{ left: 10px; } +.slider-arrow.next{ right: 10px; } + +/* ✅ 컨텐츠 좌우 패딩(화살표 영역만큼) */ +.hero-slider--compact .hero-content{ padding-left: 44px; padding-right: 44px; } + +/* dots */ +.dots-container{ position:absolute; bottom: 12px; left:50%; transform: translateX(-50%); display:flex; gap: 8px; z-index:5; } +.dot{ width: 8px; height: 8px; border-radius: 50%; background: rgba(0,0,0,0.18); } +.dot.active{ background: var(--color-accent-blue); transform: scale(1.15); } + +@media (max-width: 768px){ + .hero-slider--compact{ height: 150px; } + .hero-slider--compact .hero-content{ padding-left: 14px; padding-right: 14px; } /* 모바일에선 화살표가 위에 떠도 괜찮게 */ + .slider-arrow{ display:none; } /* 모바일은 스와이프/도트로만 */ +} +.subpage-wrap .hero-slider--compact{ + margin-top: 14px; + margin-bottom: 16px; +} + +/* ===== List Head (section heading) ===== */ +.list-head{ + display:flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin: 6px 0 12px; + + /* 은은한 구획감: 라인 + 여백만 */ + padding-bottom: 8px; + border-bottom: 1px solid #EEF2F7; +} + +.list-title{ + margin: 0; + font-size: 16px; /* 과하게 크지 않게 */ + font-weight: 900; + letter-spacing: -0.2px; + color: #0F172A; + line-height: 1.2; +} + +.list-desc{ + margin: 0; + font-size: 13px; + color: #64748B; + line-height: 1.2; + + /* 우측 정렬 + 길면 깔끔하게 처리 */ + text-align: right; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 60%; +} + +/* 모바일은 설명 숨기고 더 타이트하게 */ +@media (max-width: 560px){ + .list-head{ padding-bottom: 6px; margin: 4px 0 10px; } + .list-title{ font-size: 15px; } + .list-desc{ display:none; } +} + +/* ===== CS Tabs ===== */ +.cs-tabs{ + display:flex; + gap: 8px; + flex-wrap: wrap; + margin: 10px 0 14px; +} +.cs-tab{ + height: 34px; + padding: 0 14px; + border-radius: 9999px; + border: 1px solid #E5E7EB; + background:#fff; + font-size: 13px; + font-weight: 800; + color:#334155; +} +.cs-tab:hover{ background:#F8FAFC; } +.cs-tab.is-active{ + background:#EFF6FF; + border-color:#BFDBFE; + color:#1D4ED8; +} diff --git a/resources/js/web.js b/resources/js/web.js index 36a21b2..f0b8ec4 100644 --- a/resources/js/web.js +++ b/resources/js/web.js @@ -125,4 +125,45 @@ document.addEventListener('DOMContentLoaded', () => { alert('모바일 메뉴 드로어 열림 (구현 예정)'); }); } + + // --- Sub Hero Carousel --- + (function initSubHero(){ + const wrap = document.querySelector('.subhero-inner'); + if(!wrap) return; + + const track = wrap.querySelector('.subhero-track'); + const slides = wrap.querySelectorAll('.subhero-slide'); + const prev = wrap.querySelector('.subhero-arrow.prev'); + const next = wrap.querySelector('.subhero-arrow.next'); + const dots = wrap.querySelectorAll('.subhero-dot'); + + let idx = 0; + const total = slides.length; + if(total <= 1) return; + + const render = () => { + track.style.transform = `translateX(-${idx * 100}%)`; + dots.forEach((d,i)=>d.classList.toggle('active', i===idx)); + }; + + const goNext = () => { idx = (idx + 1) % total; render(); }; + const goPrev = () => { idx = (idx - 1 + total) % total; render(); }; + + let t = setInterval(goNext, 6000); + + const reset = () => { clearInterval(t); t = setInterval(goNext, 6000); }; + + next?.addEventListener('click', ()=>{ goNext(); reset(); }); + prev?.addEventListener('click', ()=>{ goPrev(); reset(); }); + dots.forEach(d => d.addEventListener('click', ()=>{ + idx = Number(d.dataset.index || 0); + render(); reset(); + })); + + wrap.addEventListener('mouseenter', ()=>clearInterval(t)); + wrap.addEventListener('mouseleave', ()=>{ t = setInterval(goNext, 6000); }); + + render(); + })(); + }); diff --git a/resources/views/web/company/header.blade.php b/resources/views/web/company/header.blade.php index 6ce8850..f45fe69 100644 --- a/resources/views/web/company/header.blade.php +++ b/resources/views/web/company/header.blade.php @@ -13,8 +13,8 @@ HOME SHOP {{-- 상품권현금교환--}} - 마이페이지 - 고객센터 + 마이페이지 + 고객센터 diff --git a/resources/views/web/cs/faq/index.blade.php b/resources/views/web/cs/faq/index.blade.php new file mode 100644 index 0000000..b82083d --- /dev/null +++ b/resources/views/web/cs/faq/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '자주 묻는 질문'; + $pageDesc = '결제/발송/환불 등 자주 문의되는 내용을 빠르게 확인하세요.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '고객센터', 'url' => url('/cs')], + ['label' => '자주 묻는 질문', 'url' => url()->current()], + ]; + + $csActive = 'faq'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '자주 묻는 질문(FAQ) | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU FAQ입니다. 결제/발송/환불/이용 관련 자주 묻는 질문을 확인하세요.') +@section('canonical', url('/cs/faq')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => 'FAQ', + 'desc' => '원하시는 항목을 선택해 빠르게 해결해 보세요.' + ]) + + {{-- TODO: FAQ 내용(아코디언/카테고리/검색 등) --}} +
+@endsection diff --git a/resources/views/web/cs/guide/index.blade.php b/resources/views/web/cs/guide/index.blade.php new file mode 100644 index 0000000..3290764 --- /dev/null +++ b/resources/views/web/cs/guide/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '이용안내'; + $pageDesc = '구매부터 발송/환불까지 서비스 이용 방법을 한눈에 안내합니다.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '고객센터', 'url' => url('/cs')], + ['label' => '이용안내', 'url' => url()->current()], + ]; + + $csActive = 'guide'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '이용안내 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 이용안내입니다. 상품권 구매/발송/환불 등 이용 방법을 확인하세요.') +@section('canonical', url('/cs/guide')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '서비스 이용 안내', + 'desc' => '주요 흐름(구매 → 결제 → 발송 → 환불)을 기준으로 안내합니다.' + ]) + + {{-- TODO: 이용안내 본문(단계별 가이드/주의사항/정책 링크 등) --}} +
+@endsection diff --git a/resources/views/web/cs/kakao/index.blade.php b/resources/views/web/cs/kakao/index.blade.php new file mode 100644 index 0000000..20cc127 --- /dev/null +++ b/resources/views/web/cs/kakao/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '카카오톡 상담'; + $pageDesc = '카카오톡으로 빠르고 편하게 문의하실 수 있어요.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '고객센터', 'url' => url('/cs')], + ['label' => '카카오톡 상담', 'url' => url()->current()], + ]; + + $csActive = 'kakao'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '카카오톡 상담 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 카카오톡 상담 안내입니다. 운영시간과 상담 절차를 확인하세요.') +@section('canonical', url('/cs/kakao')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '카카오톡 상담 안내', + 'desc' => '운영시간과 안내사항을 확인하고 빠르게 문의해 주세요.' + ]) + + {{-- TODO: 카카오 채널 링크/QR/운영시간/주의사항 등 --}} +
+@endsection diff --git a/resources/views/web/cs/notice/index.blade.php b/resources/views/web/cs/notice/index.blade.php new file mode 100644 index 0000000..50947f4 --- /dev/null +++ b/resources/views/web/cs/notice/index.blade.php @@ -0,0 +1,66 @@ +@php + $pageTitle = '공지사항'; + $pageDesc = '서비스 운영 및 결제/발송 관련 안내를 확인하세요.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '고객센터', 'url' => url('/cs')], + ['label' => '공지사항', 'url' => url()->current()], + ]; + + // ✅ 이것만 남기면 됨 (탭 목록은 config에서 자동) + $csActive = 'notice'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '공지사항 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 공지사항입니다. 운영/결제/발송 관련 최신 안내를 확인하세요.') +@section('canonical', url('/cs/notice')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '최신 공지', + 'desc' => '중요 안내를 빠르게 확인하세요.' + ]) + + + + {{-- Notice Toolbar (replace sub-quick) --}} + +
+@endsection diff --git a/resources/views/web/cs/qna/index.blade.php b/resources/views/web/cs/qna/index.blade.php new file mode 100644 index 0000000..2f31e67 --- /dev/null +++ b/resources/views/web/cs/qna/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '1:1 문의'; + $pageDesc = '개인 문의는 1:1로 남겨주시면 순차적으로 답변드립니다.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '고객센터', 'url' => url('/cs')], + ['label' => '1:1 문의', 'url' => url()->current()], + ]; + + $csActive = 'qna'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '1:1 문의 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 1:1 문의 페이지입니다. 문의를 남겨주시면 순차적으로 답변드립니다.') +@section('canonical', url('/cs/qna')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '문의하기', + 'desc' => '문의 내용을 남겨주시면 확인 후 빠르게 안내드리겠습니다.' + ]) + + {{-- TODO: 문의 폼 / 내 문의 내역 / 로그인 여부 처리 등 --}} +
+@endsection diff --git a/resources/views/web/home.blade.php b/resources/views/web/home.blade.php index b9e26ba..b0414bd 100644 --- a/resources/views/web/home.blade.php +++ b/resources/views/web/home.blade.php @@ -3,7 +3,17 @@ @section('content') {{-- Hero --}} - @include('web.main.hero-carousel') + @php + $heroSlides = config('web.hero_slides', []); + @endphp + + @if(!empty($heroSlides)) + @include('web.partials.hero-slider', [ + 'slides' => $heroSlides, + 'variant' => 'main', + 'id' => 'hero-main' + ]) + @endif {{-- Quick Categories --}} @include('web.main.quick-categories') diff --git a/resources/views/web/layouts/layout.blade.php b/resources/views/web/layouts/layout.blade.php index cd90ee5..964e5f2 100644 --- a/resources/views/web/layouts/layout.blade.php +++ b/resources/views/web/layouts/layout.blade.php @@ -4,22 +4,54 @@ - Voucher Mall - 상품권 최저가 쇼핑 - - {{-- SEO --}} - - - - {{-- CSRF (추후 폼/로그인 대비) --}} + {{-- CSRF --}} - {{-- Pretendard Font --}} - - + {{-- Canonical/Title/Description (중복 금지) --}} + @php + $defaultTitle = 'PIN FOR YOU'; + $defaultDesc = '상품권, 모바일 교환권, 구글플레이, 문화상품권 등 다양한 모바일 쿠폰을 안전하고 빠르게 할인 구매하세요.'; + $canonicalUrl = trim($__env->yieldContent('canonical', url()->current())); + @endphp - {{-- ✅ WEB 전용 번들 로딩 --}} + @yield('title', $defaultTitle) + + + + + {{-- Open Graph --}} + + + + + + + {{-- 대표 이미지 준비되면 사용 (없으면 생략 가능) + + --}} + + {{-- Twitter Card (선택이지만 권장) --}} + + + + {{-- --}} + + {{-- (선택) 기본 키워드는 큰 의미 없어서 생략 권장 --}} + {{-- --}} + + {{-- Font --}} + + + + {{-- Web Bundle --}} @vite(['resources/css/web.css', 'resources/js/web.js']) + + {{-- 페이지별 추가 head --}} + @yield('head') + {{-- Header --}} diff --git a/resources/views/web/layouts/subpage.blade.php b/resources/views/web/layouts/subpage.blade.php new file mode 100644 index 0000000..d029cde --- /dev/null +++ b/resources/views/web/layouts/subpage.blade.php @@ -0,0 +1,143 @@ +@extends('web.layouts.layout') + +@section('content') +
+
+ + {{-- Breadcrumb (optional) --}} + @isset($breadcrumbs) + @include('web.partials.breadcrumb', ['items' => $breadcrumbs]) + @endisset + + @php + $heroSlides = config('web.hero_slides', []); + + // ✅ 섹션 감지 + $isCsPage = request()->is('cs') || request()->is('cs/*') || request()->routeIs('web.cs.*'); + $isMypagePage = request()->is('mypage') || request()->is('mypage/*') || request()->routeIs('web.mypage.*'); + $isPolicyPage = request()->is('policy') || request()->is('policy/*') || request()->routeIs('web.policy.*'); + + // ✅ CS subnav 자동 주입 + if ($isCsPage && empty($subnavItems)) { + $subnavItems = collect(config('web.cs_tabs', [])) + ->map(function ($t) { + $url = '#'; + if (!empty($t['route']) && \Illuminate\Support\Facades\Route::has($t['route'])) { + $url = route($t['route']); + } + return [ + 'label' => $t['label'] ?? '', + 'url' => $url, + 'key' => $t['key'] ?? null, + ]; + }) + ->values() + ->all(); + } + + // ✅ MYPAGE subnav 자동 주입 + if ($isMypagePage && empty($subnavItems)) { + $subnavItems = collect(config('web.mypage_tabs', [])) + ->map(function ($t) { + $url = '#'; + if (!empty($t['route']) && \Illuminate\Support\Facades\Route::has($t['route'])) { + $url = route($t['route']); + } + return [ + 'label' => $t['label'] ?? '', + 'url' => $url, + 'key' => $t['key'] ?? null, + ]; + }) + ->values() + ->all(); + } + + // ✅ PolicyPage subnav 자동 주입 + if ($isPolicyPage && empty($subnavItems)) { + $subnavItems = collect(config('web.policy_tabs', [])) + ->map(function ($t) { + $url = '#'; + if (!empty($t['route']) && \Illuminate\Support\Facades\Route::has($t['route'])) { + $url = route($t['route']); + } + return [ + 'label' => $t['label'] ?? '', + 'url' => $url, + 'key' => $t['key'] ?? null, + ]; + }) + ->values() + ->all(); + } + + // ✅ active key 우선순위: 각 섹션 전용 변수 -> 기존 subnavActive + $subnavActive = $csActive ?? $mypageActive ?? ($subnavActive ?? null); + @endphp + + @if(!empty($heroSlides)) + @include('web.partials.hero-slider', [ + 'slides' => $heroSlides, + 'variant' => 'compact', + 'id' => 'hero-sub' + ]) + @endif + + {{-- Header --}} + @include('web.partials.subpage-header', [ + 'title' => $pageTitle ?? '페이지 제목', + 'desc' => $pageDesc ?? null + ]) + + {{-- Body --}} +
+ + {{-- ✅ 고객센터면 cs-tabs --}} + @if($isCsPage) + @include('web.partials.cs-tabs', [ + 'activeKey' => $subnavActive + ]) + + {{-- ✅ 마이페이지면 mypage-tabs --}} + @elseif($isMypagePage) + @include('web.partials.mypage-tabs', [ + 'activeKey' => $subnavActive + ]) + + @elseif($isPolicyPage) + @include('web.partials.policy-tabs', [ + 'activeKey' => $subnavActive + ]) + + {{-- 그 외 일반 서브페이지는 기존 방식 유지 --}} + @else + {{-- Mobile Tabs --}} +
+ @include('web.partials.subpage-sidenav', [ + 'items' => $subnavItems ?? [], + 'active' => $subnavActive ?? null, + 'mode' => 'tabs' + ]) +
+ + {{-- Desktop Side --}} + + @endif + + {{-- Main --}} +
+
+ @yield('subcontent') +
+
+ +
+
+
+@endsection diff --git a/resources/views/web/main/hero-carousel.blade.php b/resources/views/web/main/hero-carousel.blade.php deleted file mode 100644 index 244ef55..0000000 --- a/resources/views/web/main/hero-carousel.blade.php +++ /dev/null @@ -1,52 +0,0 @@ -
-
- -
-
- 특별 - 프로모션 -

구글플레이 기프트카드
최대 12% 즉시 할인

-

인기 게임 아이템부터 영화까지, 더 저렴하게 즐기세요.

- 지금 구매하기 -
-
- -
-
- 신규 입점 -

문화상품권 24시간
자동 발송 시스템 오픈

-

기다림 없이 결제 즉시 문자로 핀번호를 받아보세요.

- 상품 보러가기 -
-
- -
-
- 한정 수량 -

편의점 모바일 금액권
5만원권 10% 핫딜

-

CU, GS25, 세븐일레븐 전국 어디서나 사용 가능

- 구매하기 -
-
-
- - - - -
- -
-
-
-
-
\ No newline at end of file diff --git a/resources/views/web/partials/breadcrumb.blade.php b/resources/views/web/partials/breadcrumb.blade.php new file mode 100644 index 0000000..2b44edc --- /dev/null +++ b/resources/views/web/partials/breadcrumb.blade.php @@ -0,0 +1,33 @@ +@props(['items' => []]) + + + +{{-- SEO: Breadcrumb JSON-LD --}} +@php + $ld = [ + "@context" => "https://schema.org", + "@type" => "BreadcrumbList", + "itemListElement" => [] + ]; + foreach($items as $idx => $it){ + $ld["itemListElement"][] = [ + "@type" => "ListItem", + "position" => $idx+1, + "name" => $it["label"], + "item" => $it["url"] ?? url()->current() + ]; + } +@endphp + diff --git a/resources/views/web/partials/content-head.blade.php b/resources/views/web/partials/content-head.blade.php new file mode 100644 index 0000000..e7c598a --- /dev/null +++ b/resources/views/web/partials/content-head.blade.php @@ -0,0 +1,8 @@ +@props(['title' => '', 'desc' => null]) + +
+

{{ $title }}

+ @if($desc) +

{{ $desc }}

+ @endif +
diff --git a/resources/views/web/partials/cs-tabs.blade.php b/resources/views/web/partials/cs-tabs.blade.php new file mode 100644 index 0000000..1e16b54 --- /dev/null +++ b/resources/views/web/partials/cs-tabs.blade.php @@ -0,0 +1,44 @@ +@php + // config에서 CS 탭 가져와서, 기존 subpage-sidenav가 기대하는 형식(label,url,key)으로 변환 + $items = collect(config('web.cs_tabs', [])) + ->map(function ($t) { + return [ + 'label' => $t['label'] ?? '', + 'url' => isset($t['route']) ? route($t['route']) : '#', + 'key' => $t['key'] ?? null, + ]; + }) + ->values() + ->all(); + + // activeKey는 페이지에서 넘기거나, 없으면 현재 route로 자동 판별도 가능 + $activeKey = $activeKey ?? null; + + if (!$activeKey) { + // route 기반 자동 active (routeIs가 동작하려면 routes에 name이 있어야 함) + foreach (config('web.cs_tabs', []) as $t) { + if (!empty($t['route']) && request()->routeIs($t['route'])) { + $activeKey = $t['key'] ?? null; + break; + } + } + } +@endphp + +{{-- Mobile Tabs --}} +
+ @include('web.partials.subpage-sidenav', [ + 'items' => $items, + 'active' => $activeKey, + 'mode' => 'tabs' + ]) +
+ +{{-- Desktop Side --}} + diff --git a/resources/views/web/partials/hero-slider.blade.php b/resources/views/web/partials/hero-slider.blade.php new file mode 100644 index 0000000..6b21f80 --- /dev/null +++ b/resources/views/web/partials/hero-slider.blade.php @@ -0,0 +1,43 @@ +@props([ + 'slides' => [], + 'variant' => 'main', // 'main' | 'compact' + 'id' => 'hero' +]) +@if(!empty($slides)) +
+
+ @foreach($slides as $s) +
+
+ @if(!empty($s['kicker'])) +
{{ $s['kicker'] }}
+ @endif + +

{{ $s['title'] }}

+ + @if(!empty($s['desc'])) +

{{ $s['desc'] }}

+ @endif + + @if(!empty($s['cta_label']) && !empty($s['cta_url'])) + + {{ $s['cta_label'] }} + + @endif +
+
+ @endforeach +
+ + @if(count($slides) > 1) + + + +
+ @foreach($slides as $idx => $s) + + @endforeach +
+ @endif +
+@endif diff --git a/resources/views/web/partials/mypage-tabs.blade.php b/resources/views/web/partials/mypage-tabs.blade.php new file mode 100644 index 0000000..85aa6f1 --- /dev/null +++ b/resources/views/web/partials/mypage-tabs.blade.php @@ -0,0 +1,45 @@ +@php + $rawTabs = config('web.mypage_tabs', []); + + $items = collect($rawTabs)->map(function ($t) { + $url = '#'; + if (!empty($t['route']) && \Illuminate\Support\Facades\Route::has($t['route'])) { + $url = route($t['route']); + } + return [ + 'label' => $t['label'] ?? '', + 'url' => $url, + 'key' => $t['key'] ?? null, + ]; + })->values()->all(); + + $activeKey = $activeKey ?? null; + + // activeKey가 없으면 현재 라우트로 자동 판별 + if (!$activeKey) { + foreach ($rawTabs as $t) { + if (!empty($t['route']) && request()->routeIs($t['route'])) { + $activeKey = $t['key'] ?? null; + break; + } + } + } +@endphp + +{{-- Mobile Tabs --}} +
+ @include('web.partials.subpage-sidenav', [ + 'items' => $items, + 'active' => $activeKey, + 'mode' => 'tabs' + ]) +
+ +{{-- Desktop Side --}} + diff --git a/resources/views/web/partials/policy-tabs.blade.php b/resources/views/web/partials/policy-tabs.blade.php new file mode 100644 index 0000000..893c68b --- /dev/null +++ b/resources/views/web/partials/policy-tabs.blade.php @@ -0,0 +1,44 @@ +@php + $rawTabs = config('web.policy_tabs', []); + + $items = collect($rawTabs)->map(function ($t) { + $url = '#'; + if (!empty($t['route']) && \Illuminate\Support\Facades\Route::has($t['route'])) { + $url = route($t['route']); + } + return [ + 'label' => $t['label'] ?? '', + 'url' => $url, + 'key' => $t['key'] ?? null, + ]; + })->values()->all(); + + $activeKey = $activeKey ?? null; + + if (!$activeKey) { + foreach ($rawTabs as $t) { + if (!empty($t['route']) && request()->routeIs($t['route'])) { + $activeKey = $t['key'] ?? null; + break; + } + } + } +@endphp + +{{-- Mobile Tabs --}} +
+ @include('web.partials.subpage-sidenav', [ + 'items' => $items, + 'active' => $activeKey, + 'mode' => 'tabs' + ]) +
+ +{{-- Desktop Side --}} + diff --git a/resources/views/web/partials/subpage-header.blade.php b/resources/views/web/partials/subpage-header.blade.php new file mode 100644 index 0000000..e7fc8e3 --- /dev/null +++ b/resources/views/web/partials/subpage-header.blade.php @@ -0,0 +1,4 @@ +
+

{{ $title }}

+ @if($desc)

{{ $desc }}

@endif +
diff --git a/resources/views/web/partials/subpage-sidenav.blade.php b/resources/views/web/partials/subpage-sidenav.blade.php new file mode 100644 index 0000000..305e4ec --- /dev/null +++ b/resources/views/web/partials/subpage-sidenav.blade.php @@ -0,0 +1,29 @@ +@props(['items' => [], 'active' => null, 'mode' => 'side']) + +@if($mode === 'tabs') + +@else + +@endif diff --git a/resources/views/web/policy/email/index.blade.php b/resources/views/web/policy/email/index.blade.php new file mode 100644 index 0000000..4412e6a --- /dev/null +++ b/resources/views/web/policy/email/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '이메일무단수집거부'; + $pageDesc = '웹사이트 내 이메일 주소의 무단 수집을 거부합니다.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '약관 및 정책', 'url' => url('/policy')], + ['label' => '이메일무단수집거부', 'url' => url()->current()], + ]; + + $policyActive = 'email'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '이메일무단수집거부 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 이메일무단수집거부 안내입니다. 무단 수집 및 이용에 대한 조치를 안내합니다.') +@section('canonical', url('/policy/email')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '무단 수집 금지 안내', + 'desc' => '자동 수집 프로그램 등을 통한 이메일 주소 수집을 금지합니다.' + ]) + + {{-- TODO: 이메일무단수집거부 본문 --}} +
+@endsection diff --git a/resources/views/web/policy/index.blade.php b/resources/views/web/policy/index.blade.php new file mode 100644 index 0000000..8011a1b --- /dev/null +++ b/resources/views/web/policy/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '이용약관'; + $pageDesc = '서비스 이용 조건, 책임 범위, 분쟁 처리 기준 안내.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '약관 및 정책', 'url' => url('/policy')], + ['label' => '이용약관', 'url' => url()->current()], + ]; + + $policyActive = 'terms'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '이용약관 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 이용약관입니다. 서비스 이용 조건과 책임 범위, 분쟁 처리 기준을 안내합니다.') +@section('canonical', url('/policy/terms')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '서비스 이용 규칙', + 'desc' => '회원 의무, 거래/결제, 환불, 제재 기준을 포함합니다.' + ]) + + {{-- TODO: 이용약관 본문 --}} +
+@endsection diff --git a/resources/views/web/policy/privacy/index.blade.php b/resources/views/web/policy/privacy/index.blade.php new file mode 100644 index 0000000..278531e --- /dev/null +++ b/resources/views/web/policy/privacy/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '개인정보처리방침'; + $pageDesc = '개인정보 수집/이용/보관/파기 및 이용자 권리 안내.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '약관 및 정책', 'url' => url('/policy')], + ['label' => '개인정보처리방침', 'url' => url()->current()], + ]; + + $policyActive = 'privacy'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '개인정보처리방침 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 개인정보처리방침입니다. 개인정보 수집/이용/보관/파기 및 이용자 권리를 안내합니다.') +@section('canonical', url('/policy/privacy')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '개인정보 처리 기준', + 'desc' => '수집 목적, 보관 기간, 제공/위탁, 이용자 권리 내용을 포함합니다.' + ]) + + {{-- TODO: 개인정보처리방침 본문 --}} +
+@endsection diff --git a/resources/views/web/policy/terms/index.blade.php b/resources/views/web/policy/terms/index.blade.php new file mode 100644 index 0000000..278531e --- /dev/null +++ b/resources/views/web/policy/terms/index.blade.php @@ -0,0 +1,29 @@ +@php + $pageTitle = '개인정보처리방침'; + $pageDesc = '개인정보 수집/이용/보관/파기 및 이용자 권리 안내.'; + + $breadcrumbs = [ + ['label' => '홈', 'url' => url('/')], + ['label' => '약관 및 정책', 'url' => url('/policy')], + ['label' => '개인정보처리방침', 'url' => url()->current()], + ]; + + $policyActive = 'privacy'; +@endphp + +@extends('web.layouts.subpage') + +@section('title', '개인정보처리방침 | PIN FOR YOU') +@section('meta_description', 'PIN FOR YOU 개인정보처리방침입니다. 개인정보 수집/이용/보관/파기 및 이용자 권리를 안내합니다.') +@section('canonical', url('/policy/privacy')) + +@section('subcontent') +
+ @include('web.partials.content-head', [ + 'title' => '개인정보 처리 기준', + 'desc' => '수집 목적, 보관 기간, 제공/위탁, 이용자 권리 내용을 포함합니다.' + ]) + + {{-- TODO: 개인정보처리방침 본문 --}} +
+@endsection diff --git a/routes/web.php b/routes/web.php index 56b6833..ce54d58 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,4 +7,26 @@ Route::get('/', function () { Route::view('/', 'web.home')->name('web.home'); +Route::prefix('cs')->name('web.cs.')->group(function () { + Route::view('/notice', 'web.cs.notice.index')->name('notice.index'); + Route::view('/faq', 'web.cs.faq.index')->name('faq.index'); + Route::view('/kakao', 'web.cs.kakao.index')->name('kakao.index'); + Route::view('/qna', 'web.cs.qna.index')->name('qna.index'); + Route::view('/guide', 'web.cs.guide.index')->name('guide.index'); +}); + +Route::prefix('mypage')->name('web.mypage.')->group(function () { + Route::view('/info', 'web.mypage.info.index')->name('info.index'); + Route::view('/usage', 'web.mypage.usage.index')->name('usage.index'); + Route::view('/exchange', 'web.mypage.exchange.index')->name('exchange.index'); + Route::view('/qna', 'web.mypage.qna.index')->name('qna.index'); +}); + +Route::prefix('policy')->name('web.policy.')->group(function () { + Route::view('/', 'web.policy.index')->name('index'); + Route::view('/privacy', 'web.policy.privacy.index')->name('privacy.index'); + Route::view('/terms', 'web.policy.terms.index')->name('terms.index'); + Route::view('/email', 'web.policy.email.index')->name('email.index'); +}); + Route::fallback(fn () => abort(404));