모바일 반응형 페이지 css 수정 및 세부페이지 작업

This commit is contained in:
sungro815 2026-01-12 14:49:08 +09:00
parent 8d7d25cce8
commit 014597e4c2
6 changed files with 525 additions and 16 deletions

View File

@ -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' => '문의 및 안내', // 필요하면 '안내/문의' 같은 문구 넣어도 됨

View File

@ -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);
}

View File

@ -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(){

View File

@ -37,14 +37,19 @@
<a href="/register" class="btn btn-primary" style="padding: 8px 20px;">회원가입</a>
</div>
<!-- Mobile Menu Toggle (Hidden on Desktop) -->
<button class="mobile-menu-btn" aria-label="메뉴 열기" style="display: none;">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16">
</path>
<!-- Mobile Menu Toggle -->
<button class="mobile-menu-btn" type="button" aria-label="메뉴 열기" aria-controls="mobile-drawer" aria-expanded="false">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
<!-- Mobile Drawer -->
@include('web.partials.mobile-drawer')
<!-- Mobile Drawer Overlay -->
<div class="mobile-drawer-overlay" data-drawer-close hidden></div>
</div>
</header>

View File

@ -0,0 +1,97 @@
@php
$sections = config('web.drawer_sections', []);
$isLoggedIn = auth()->check();
$user = auth()->user();
@endphp
{{-- Overlay --}}
<div class="mobile-drawer-overlay" data-drawer-close hidden></div>
{{-- Drawer --}}
<aside class="mobile-drawer" id="mobile-drawer" aria-hidden="true">
<div class="mobile-drawer__head">
<div class="mobile-drawer__title">PIN FOR YOU</div>
<button class="mobile-drawer__close" type="button" aria-label="메뉴 닫기" data-drawer-close></button>
</div>
{{-- 트렌디한 “프로필 카드”: 로그인 / UI 분기 --}}
<div class="m-usercard">
@if($isLoggedIn)
<div class="m-usercard__row">
<div class="m-avatar" aria-hidden="true">{{ mb_substr($user->name ?? 'U', 0, 1) }}</div>
<div class="m-usercard__info">
<div class="m-usercard__name">{{ $user->name ?? '회원' }}</div>
<div class="m-usercard__meta">{{ $user->email ?? '' }}</div>
</div>
</div>
<div class="m-usercard__actions">
<a class="m-pill" href="{{ route('web.mypage.info.index') }}"> 정보</a>
{{-- <a class="m-pill m-pill--ghost" href="{{ route('web.auth.logout') }}">로그아웃</a> --}}
</div>
@else
<div class="m-usercard__row">
<div class="m-avatar m-avatar--ghost" aria-hidden="true">🔒</div>
<div class="m-usercard__info">
<div class="m-usercard__name">로그인이 필요해요</div>
<div class="m-usercard__meta">내역 확인/1:1문의는 로그인 이용 가능</div>
</div>
</div>
<div class="m-usercard__actions">
<a class="m-pill" href="{{ route('web.auth.login') }}">로그인</a>
<a class="m-pill m-pill--ghost" href="{{ route('web.auth.register') }}">회원가입</a>
</div>
@endif
</div>
{{-- 메뉴 섹션(주메뉴/CS/정책/마이페이지) --}}
<div class="mobile-drawer__body">
@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)
{{-- 로그인 전에는 마이페이지 섹션은 보여주되, 안내 문구를 --}}
<section class="m-navsec">
<div class="m-navsec__title">{{ $title }}</div>
<div class="m-navsec__hint">로그인 이용내역/교환내역을 확인할 있어요.</div>
<div class="m-grid">
<a class="m-pill" href="{{ route('web.auth.login') }}">로그인</a>
<a class="m-pill m-pill--ghost" href="{{ route('web.auth.register') }}">회원가입</a>
</div>
</section>
@continue
@endif
<section class="m-navsec">
<div class="m-navsec__title">{{ $title }}</div>
<div class="m-grid">
@foreach($items as $it)
<a class="m-tile" href="{{ $it['url'] }}">{{ $it['label'] }}</a>
@endforeach
</div>
</section>
@endforeach
</div>
{{-- Footer CTA --}}
<div class="mobile-drawer__foot">
<a class="mobile-drawer__cta" href="{{ route('web.cs.qna.index') }}">1:1 문의하기</a>
</div>
</aside>

View File

@ -1,10 +1,6 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('web.home');
})->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));