diff --git a/config/web.php b/config/web.php index f328bd0..297d3d8 100644 --- a/config/web.php +++ b/config/web.php @@ -25,6 +25,21 @@ return [ ], ], + 'main_nav' => [ + ['label' => '홈', 'route' => 'web.home', 'key' => 'home'], + ['label' => '상품권 구매', 'route' => 'web.buy.index', 'key' => 'buy'], + ['label' => '상품권 판매', 'route' => 'web.sell.index', 'key' => 'sell'], + ['label' => '거래내역', 'route' => 'web.trade.index', 'key' => 'trade'], + ], + + // 모바일 드로어에서 보여줄 “섹션 구성” + 'drawer_sections' => [ + 'main' => ['title' => '메뉴', 'items' => 'main_nav'], + 'cs' => ['title' => '고객센터', 'items' => 'cs_tabs'], + 'policy' => ['title' => '약관 및 정책', 'items' => 'policy_tabs'], + 'mypage' => ['title' => '마이페이지', 'items' => 'mypage_tabs'], + ], + 'cs_nav' => [ 'title' => '고객센터', 'subtitle' => '문의 및 안내', // 필요하면 '안내/문의' 같은 문구 넣어도 됨 diff --git a/resources/css/web.css b/resources/css/web.css index 08954aa..1694d63 100644 --- a/resources/css/web.css +++ b/resources/css/web.css @@ -1222,6 +1222,7 @@ h1, h2, h3, h4, h5, h6 { gap: 12px; margin: 0px 0 16px; padding-left:8px; + border-bottom: 1px solid rgba(0,0,0,.08); } .subpage-title{ font-size: 18px; @@ -2377,3 +2378,358 @@ img, video, svg, iframe, canvas { max-width: 100%; height: auto; } /* If any component uses nowrap, allow scroll inside it */ .policy-toc{ max-width: 100%; } +/* ========================= + Mobile Tabs (subnav--tabs) + - no horizontal overflow + - centered text + - menu-like segmented buttons + ========================= */ + +/* 기본: 모바일에서 2열 그리드(안전하게 안 넘침) */ +.subnav--tabs{ + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + width: 100%; + max-width: 100%; + margin: 0; +} + +/* 화면 조금 넓으면 3열 */ +@media (min-width: 420px){ + .subnav--tabs{ + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +/* 탭(메뉴) 버튼 */ +.subnav-tab{ + display: flex; + align-items: center; /* ✅ 세로 중앙 */ + justify-content: center; /* ✅ 가로 중앙 */ + text-align: center; + + min-width: 0; /* ✅ grid item overflow 방지 */ + min-height: 42px; + + padding: 10px 10px; + border-radius: 14px; + + border: 1px solid rgba(0,0,0,.10); + background: #fff; + + font-size: 13px; + font-weight: 800; + letter-spacing: -0.02em; + + color: rgba(0,0,0,.78); + text-decoration: none; + + /* ✅ 한 줄 유지 + 길면 ... */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + /* ✅ '메뉴'같은 클릭감 */ + transition: background-color .12s ease, border-color .12s ease, color .12s ease, transform .06s ease; +} + +.subnav-tab:hover{ + background: rgba(0,0,0,.02); +} + +.subnav-tab:active{ + transform: translateY(1px); +} + +/* 활성 탭 */ +.subnav-tab.is-active{ + background: rgba(11, 99, 255, .08); + border-color: rgba(11, 99, 255, .35); + color: #0b63ff; + box-shadow: inset 0 0 0 1px rgba(11, 99, 255, .18); +} + +/* 접근성: 키보드 포커스 */ +.subnav-tab:focus-visible{ + outline: 2px solid rgba(11, 99, 255, .55); + outline-offset: 2px; +} + +/* Subpage Header layout */ +.subpage-header{ + display:flex; + align-items:baseline; /* 제목/설명 기준선 맞춤 */ + gap:14px; + padding: 10px 0 14px; +} + +.subpage-title{ + margin:0; + padding-top:7px; + font-weight:900; + letter-spacing:-0.03em; +} + +.subpage-desc{ + margin:0; + color: rgba(0,0,0,.65); + line-height:1.6; + + /* ✅ 데스크톱에서는 한 줄에 잘리게(캡처처럼) */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ✅ 모바일: desc를 아래로 떨어뜨리기 */ +@media (max-width: 640px){ + .subpage-header{ + flex-direction: column; + align-items:flex-start; + gap:6px; + } + .subpage-desc{ + white-space: normal; /* 모바일에서는 줄바꿈 허용 */ + overflow: visible; + text-overflow: clip; + } +} + +/* ========================= + Mobile Drawer + ========================= */ + +/* 기본은 숨김(PC) */ +.mobile-menu-btn { display:none; } + +/* 모바일에서만 노출 */ +@media (max-width: 960px){ + .mobile-menu-btn{ + display:inline-flex; + align-items:center; + justify-content:center; + width:42px; + height:42px; + border:1px solid rgba(0,0,0,.10); + background:#fff; + border-radius:12px; + cursor:pointer; + } +} + +/* Overlay */ +.mobile-drawer-overlay{ + position:fixed; + inset:0; + background: rgba(0,0,0,.35); + z-index: 9998; +} + +/* Drawer */ +.mobile-drawer{ + position:fixed; + top:0; + right:0; + height:100dvh; + width:min(86vw, 360px); + background:#fff; + z-index:9999; + + transform: translateX(102%); + transition: transform .18s ease; + box-shadow: -16px 0 40px rgba(0,0,0,.18); + + display:flex; + flex-direction:column; +} + +.mobile-drawer.is-open{ + transform: translateX(0); +} + +.mobile-drawer__head{ + display:flex; + align-items:center; + justify-content:space-between; + padding:14px 14px; + border-bottom:1px solid rgba(0,0,0,.08); +} + +.mobile-drawer__title{ + font-weight:900; + letter-spacing:-0.02em; +} + +.mobile-drawer__close{ + width:38px; + height:38px; + border-radius:10px; + border:1px solid rgba(0,0,0,.10); + background:#fff; + cursor:pointer; +} + +.mobile-drawer__nav{ + padding:10px 10px; + display:flex; + flex-direction:column; + gap:8px; +} + +.mobile-drawer__link{ + display:flex; + align-items:center; + height:44px; + padding:0 12px; + border-radius:12px; + text-decoration:none; + color: rgba(0,0,0,.86); + border:1px solid rgba(0,0,0,.08); + background: rgba(0,0,0,.02); + font-weight:800; +} + +.mobile-drawer__link:hover{ + background: rgba(0,0,0,.04); +} + +.mobile-drawer__foot{ + margin-top:auto; + padding:12px; + border-top:1px solid rgba(0,0,0,.08); +} + +.mobile-drawer__cta{ + display:flex; + align-items:center; + justify-content:center; + height:44px; + border-radius:14px; + background:#0b63ff; + color:#fff; + text-decoration:none; + font-weight:900; +} + +/* 스크롤 잠금 */ +body.is-drawer-open{ + overflow:hidden; + touch-action:none; +} + +/* ===== Mobile Drawer Modern UI ===== */ + +.mobile-drawer__body{ + padding: 10px 12px 16px; + overflow:auto; +} + +.m-usercard{ + margin: 12px; + padding: 12px; + border-radius: 18px; + background: linear-gradient(135deg, rgba(11,99,255,.10), rgba(0,0,0,.02)); + border: 1px solid rgba(0,0,0,.08); +} + +.m-usercard__row{ + display:flex; + gap: 10px; + align-items:center; +} + +.m-avatar{ + width: 44px; + height: 44px; + border-radius: 14px; + display:flex; + align-items:center; + justify-content:center; + background: #0b63ff; + color:#fff; + font-weight: 900; +} +.m-avatar--ghost{ + background: rgba(0,0,0,.06); + color: rgba(0,0,0,.55); +} + +.m-usercard__name{ + font-weight: 900; + letter-spacing: -0.02em; +} +.m-usercard__meta{ + font-size: 12px; + color: rgba(0,0,0,.65); + margin-top: 2px; +} + +.m-usercard__actions{ + display:flex; + gap: 8px; + margin-top: 10px; +} + +.m-pill{ + height: 36px; + padding: 0 12px; + display:inline-flex; + align-items:center; + justify-content:center; + border-radius: 999px; + background:#0b63ff; + color:#fff; + text-decoration:none; + font-weight: 900; + border: 1px solid rgba(11,99,255,.35); +} +.m-pill--ghost{ + background:#fff; + color:#0b63ff; +} + +.m-navsec{ + padding: 0 12px; + margin-top: 14px; +} +.m-navsec__title{ + font-weight: 900; + margin: 0 0 8px; + letter-spacing: -0.02em; +} +.m-navsec__hint{ + font-size: 12px; + color: rgba(0,0,0,.60); + margin: -4px 0 10px; +} + +.m-grid{ + display:grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.m-tile{ + height: 35px; + border-radius: 14px; + border: 1px solid rgba(0,0,0,.08); + background: #fff; + display:flex; + align-items:center; + justify-content:center; + font-size:15px; + font-weight: 600; + text-decoration:none; + color: rgba(0,0,0,.84); + transition: transform .06s ease, background-color .12s ease; + min-width:0; + text-align:center; + padding: 0 10px; +} +.m-tile:hover{ background: rgba(0,0,0,.02); } +.m-tile:active{ transform: translateY(1px); } + +.m-tile--ghost{ + background: rgba(0,0,0,.02); +} diff --git a/resources/js/web.js b/resources/js/web.js index f0b8ec4..6b6a1d9 100644 --- a/resources/js/web.js +++ b/resources/js/web.js @@ -117,14 +117,45 @@ document.addEventListener('DOMContentLoaded', () => { }); // --- Mobile Menu Toggle --- - const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); - // In a real implementation, we would toggle a drawer here. - // For this prototype, we'll just log it or alert. - if(mobileMenuBtn) { - mobileMenuBtn.addEventListener('click', () => { - alert('모바일 메뉴 드로어 열림 (구현 예정)'); + // --- Mobile Drawer Toggle --- + (() => { + const btn = document.querySelector('.mobile-menu-btn'); + const drawer = document.querySelector('.mobile-drawer'); + const overlay = document.querySelector('.mobile-drawer-overlay'); + + if (!btn || !drawer || !overlay) return; + + const open = () => { + drawer.classList.add('is-open'); + overlay.hidden = false; + drawer.setAttribute('aria-hidden', 'false'); + btn.setAttribute('aria-expanded', 'true'); + document.body.classList.add('is-drawer-open'); + }; + + const close = () => { + drawer.classList.remove('is-open'); + overlay.hidden = true; + drawer.setAttribute('aria-hidden', 'true'); + btn.setAttribute('aria-expanded', 'false'); + document.body.classList.remove('is-drawer-open'); + }; + + btn.addEventListener('click', () => { + drawer.classList.contains('is-open') ? close() : open(); }); - } + + // overlay 클릭 or 닫기 버튼 + document.querySelectorAll('[data-drawer-close]').forEach(el => { + el.addEventListener('click', close); + }); + + // ESC 닫기 + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && drawer.classList.contains('is-open')) close(); + }); + })(); + // --- Sub Hero Carousel --- (function initSubHero(){ diff --git a/resources/views/web/company/header.blade.php b/resources/views/web/company/header.blade.php index f45fe69..553e102 100644 --- a/resources/views/web/company/header.blade.php +++ b/resources/views/web/company/header.blade.php @@ -37,14 +37,19 @@ 회원가입 - - + + + {{-- ✅ 트렌디한 “프로필 카드”: 로그인 전/후 UI 분기 --}} +
+ @if($isLoggedIn) +
+ +
+
{{ $user->name ?? '회원' }}
+
{{ $user->email ?? '' }}
+
+
+
+ 내 정보 + {{-- 로그아웃 --}} +
+ @else +
+ +
+
로그인이 필요해요
+
내역 확인/1:1문의는 로그인 후 이용 가능
+
+
+
+ 로그인 + 회원가입 +
+ @endif +
+ + {{-- ✅ 메뉴 섹션(주메뉴/CS/정책/마이페이지) --}} +
+ @foreach($sections as $secKey => $sec) + @php + $title = $sec['title'] ?? ''; + $itemsKey = $sec['items'] ?? null; + $rawItems = $itemsKey ? config('web.' . $itemsKey, []) : []; + $items = collect($rawItems)->map(function($it){ + $url = '#'; + if (!empty($it['route'])) { + try { $url = route($it['route']); } catch (\Throwable $e) { $url = '#'; } + } elseif (!empty($it['url'])) { + $url = $it['url']; + } + return [ + 'label' => $it['label'] ?? '', + 'url' => $url, + ]; + })->values()->all(); + @endphp + + {{-- 마이페이지는 “로그인 전”에 섹션 자체를 살짝 줄여 보여도 됨 --}} + @if($secKey === 'mypage' && !$isLoggedIn) + {{-- 로그인 전에는 마이페이지 섹션은 보여주되, 안내 문구를 둠 --}} + + @continue + @endif + + + @endforeach +
+ + {{-- Footer CTA --}} +
+ 1:1 문의하기 +
+ diff --git a/routes/web.php b/routes/web.php index ce54d58..4f22c25 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,10 +1,6 @@ name('web.home'); - Route::view('/', 'web.home')->name('web.home'); Route::prefix('cs')->name('web.cs.')->group(function () { @@ -29,4 +25,13 @@ Route::prefix('policy')->name('web.policy.')->group(function () { Route::view('/email', 'web.policy.email.index')->name('email.index'); }); +Route::get('/login', function () { + return view('web.auth.login'); // 너가 만든 로그인 뷰로 변경 +})->name('web.auth.login'); + +Route::get('/register', function () { + return view('web.auth.register'); // 회원가입 뷰로 변경 +})->name('web.auth.register'); + + Route::fallback(fn () => abort(404));