natice 작업
3
.gitignore
vendored
@ -51,3 +51,6 @@ Thumbs.db
|
|||||||
|
|
||||||
# --- Local docker override files (if you use them) ---
|
# --- Local docker override files (if you use them) ---
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# dev-only test lab (never commit)
|
||||||
|
app/Http/Controllers/Dev/DevLabController.php
|
||||||
|
|||||||
92
app/Http/Controllers/Web/Cs/NoticeController.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web\Cs;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\GcBoard;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class NoticeController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$q = trim((string)$request->query('q', ''));
|
||||||
|
|
||||||
|
$notices = GcBoard::query()
|
||||||
|
->visibleNotice()
|
||||||
|
->when($q !== '', function ($query) use ($q) {
|
||||||
|
$query->where(function ($w) use ($q) {
|
||||||
|
$w->where('subject', 'like', "%{$q}%")
|
||||||
|
->orWhere('content', 'like', "%{$q}%");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->noticeOrder()
|
||||||
|
->paginate(15)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
|
return view('web.cs.notice.index', [
|
||||||
|
'notices' => $notices,
|
||||||
|
'q' => $q,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, int $seq)
|
||||||
|
{
|
||||||
|
$notice = GcBoard::query()
|
||||||
|
->visibleNotice()
|
||||||
|
->where('seq', $seq)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
// 조회수 (세션 기준 중복 방지)
|
||||||
|
$hitKey = "cs_notice_hit_{$seq}";
|
||||||
|
if (!$request->session()->has($hitKey)) {
|
||||||
|
GcBoard::where('seq', $seq)->increment('hit');
|
||||||
|
$request->session()->put($hitKey, 1);
|
||||||
|
$notice->hit = (int)$notice->hit + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// base
|
||||||
|
$base = GcBoard::query()->visibleNotice();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전제: noticeOrder()가 "최신/우선 노출이 위" 정렬이라고 가정
|
||||||
|
* 예) first_sign DESC, seq DESC (또는 regdate DESC 포함)
|
||||||
|
*
|
||||||
|
* - prev: 리스트에서 '아래' 글(더 오래된/더 낮은 우선순위) => 정렬상 "뒤"
|
||||||
|
* - next: 리스트에서 '위' 글(더 최신/더 높은 우선순위) => 정렬상 "앞"
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ✅ prev (뒤쪽: first_sign 더 낮거나, 같으면 seq 더 낮은 것)
|
||||||
|
$prev = (clone $base)
|
||||||
|
->where(function ($w) use ($notice) {
|
||||||
|
$w->where('first_sign', '<', $notice->first_sign)
|
||||||
|
->orWhere(function ($w2) use ($notice) {
|
||||||
|
$w2->where('first_sign', '=', $notice->first_sign)
|
||||||
|
->where('seq', '<', $notice->seq);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->noticeOrder() // DESC 정렬 그대로 (뒤쪽 중에서 가장 가까운 1개)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// ✅ next (앞쪽: first_sign 더 높거나, 같으면 seq 더 높은 것)
|
||||||
|
// "가장 가까운 앞"을 얻기 위해 정렬을 ASC로 뒤집어서 first()로 뽑는다.
|
||||||
|
$next = (clone $base)
|
||||||
|
->where(function ($w) use ($notice) {
|
||||||
|
$w->where('first_sign', '>', $notice->first_sign)
|
||||||
|
->orWhere(function ($w2) use ($notice) {
|
||||||
|
$w2->where('first_sign', '=', $notice->first_sign)
|
||||||
|
->where('seq', '>', $notice->seq);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->orderBy('first_sign', 'asc')
|
||||||
|
->orderBy('seq', 'asc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return view('web.cs.notice.show', [
|
||||||
|
'notice' => $notice,
|
||||||
|
'prev' => $prev,
|
||||||
|
'next' => $next,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
app/Models/GcBoard.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class GcBoard extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'gc_board';
|
||||||
|
protected $primaryKey = 'seq';
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'gubun', 'subject', 'content', 'admin_num', 'regdate',
|
||||||
|
'hiding', 'first_sign', 'link_01', 'link_02', 'file_01', 'file_02', 'hit',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'seq' => 'integer',
|
||||||
|
'admin_num' => 'integer',
|
||||||
|
'first_sign' => 'integer',
|
||||||
|
'hit' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 노출 + 공지 */
|
||||||
|
public function scopeVisibleNotice($q)
|
||||||
|
{
|
||||||
|
return $q->where('gubun', 'notice')->where('hiding', 'N');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 우선노출/최신순 정렬 */
|
||||||
|
public function scopeNoticeOrder($q)
|
||||||
|
{
|
||||||
|
return $q->orderByDesc('first_sign')
|
||||||
|
->orderByDesc('regdate')
|
||||||
|
->orderByDesc('seq');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** regdate가 0000-00-00... 일 수 있어 안전 파싱 */
|
||||||
|
public function regdateCarbon(): ?Carbon
|
||||||
|
{
|
||||||
|
$v = $this->regdate;
|
||||||
|
if (!$v || $v === '0000-00-00 00:00:00') return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Carbon::parse($v);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 리스트 태그(임시 규칙): 우선노출이면 공지, 아니면 안내 */
|
||||||
|
public function uiTag(): array
|
||||||
|
{
|
||||||
|
if ((int)$this->first_sign > 0) {
|
||||||
|
return ['label' => '공지', 'class' => 'n2-tag'];
|
||||||
|
}
|
||||||
|
return ['label' => '안내', 'class' => 'n2-tag n2-tag--info'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 링크 안전 보정 */
|
||||||
|
public function safeLink(?string $url): ?string
|
||||||
|
{
|
||||||
|
$url = trim((string)$url);
|
||||||
|
if ($url === '') return null;
|
||||||
|
|
||||||
|
// http(s) 아니면 그냥 반환(내부경로일 수도 있으니)
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,28 +3,56 @@
|
|||||||
return [
|
return [
|
||||||
'hero_slides' => [
|
'hero_slides' => [
|
||||||
[
|
[
|
||||||
'kicker' => 'PIN FOR YOU',
|
'kicker' => 'PIN FOR YOU',
|
||||||
'title' => '안전하고 빠른 상품권 거래',
|
'title' => '안전하고 빠른 상품권 거래',
|
||||||
'desc' => '구글플레이·문화상품권·편의점 등 인기 상품을 할인 구매하세요.',
|
'desc' => '가장 저렴한 온라인 상품권 판매. 구글플레이·문화상품권·편의점 등 인기 상품을 할인 구매하세요.',
|
||||||
'cta_label' => '상품 보러가기',
|
'cta_label' => '상품 보러가기',
|
||||||
'cta_url' => '/shop',
|
'cta_url' => '/shop',
|
||||||
|
'image' => [
|
||||||
|
'default' => '/assets/images/common/hero/hero-01-shop.webp',
|
||||||
|
'mobile' => '/assets/images/common/hero/hero-01-shop.webp',
|
||||||
|
],
|
||||||
|
'bg_pos' => 'right center', // 선택: cover일 때 포커스
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'kicker' => 'EVENT',
|
'kicker' => 'BENEFIT',
|
||||||
'title' => '카드/휴대폰 결제 지원',
|
'title' => '카드/휴대폰 결제 지원',
|
||||||
'desc' => '원하는 결제수단으로 편하게, 발송은 빠르게.',
|
'desc' => '원하는 결제수단으로 편하게 결제하고, 발송은 빠르게 받아보세요.',
|
||||||
'cta_label' => '결제 안내',
|
'cta_label' => '상품 보러가기',
|
||||||
'cta_url' => '/guide',
|
'cta_url' => '/shop',
|
||||||
|
'image' => [
|
||||||
|
'default' => '/assets/images/common/hero/hero-02-pay.webp',
|
||||||
|
'mobile' => '/assets/images/common/hero/hero-02-pay.webp',
|
||||||
|
],
|
||||||
|
'bg_pos' => 'right center',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'kicker' => 'SUPPORT',
|
'kicker' => 'SUPPORT',
|
||||||
'title' => '문제 생기면 1:1 문의',
|
'title' => '카카오 문의 · FAQ · 1:1 문의',
|
||||||
'desc' => '빠른 응답으로 도와드릴게요.',
|
'desc' => '자주 묻는 질문부터 카카오 상담, 1:1 문의까지 빠르게 도와드릴게요.',
|
||||||
'cta_label' => '1:1 문의하기',
|
'cta_label' => '고객센터',
|
||||||
'cta_url' => '/cs/qna',
|
'cta_url' => '/cs/faq',
|
||||||
|
'image' => [
|
||||||
|
'default' => '/assets/images/common/hero/hero-03-support.webp',
|
||||||
|
'mobile' => '/assets/images/common/hero/hero-03-support.webp',
|
||||||
|
],
|
||||||
|
'bg_pos' => 'right center',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'kicker' => 'SPECIAL',
|
||||||
|
'title' => '핀포유만의 특별혜택',
|
||||||
|
'desc' => '출금 수수료 무료 + 매입 서비스 운영. 컬쳐랜드 상품권 92% 매입.',
|
||||||
|
'cta_label' => '매입 서비스',
|
||||||
|
'cta_url' => '/buy',
|
||||||
|
'image' => [
|
||||||
|
'default' => '/assets/images/common/hero/hero-04-special.webp',
|
||||||
|
'mobile' => '/assets/images/common/hero/hero-04-special.webp',
|
||||||
|
],
|
||||||
|
'bg_pos' => 'right center',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
//에인메뉴
|
||||||
'main_nav' => [
|
'main_nav' => [
|
||||||
['label' => '홈', 'route' => 'web.home', 'key' => 'home'],
|
['label' => '홈', 'route' => 'web.home', 'key' => 'home'],
|
||||||
['label' => '상품권 구매', 'route' => 'web.buy.index', 'key' => 'buy'],
|
['label' => '상품권 구매', 'route' => 'web.buy.index', 'key' => 'buy'],
|
||||||
|
|||||||
372
public/assets/css/cs-notice.css
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
/* pager */
|
||||||
|
.notice-pager{
|
||||||
|
margin-top: 14px;
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg{
|
||||||
|
display:flex; align-items:center; gap:10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(255,255,255,.10);
|
||||||
|
background: rgba(255,255,255,.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg-btn{
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(255,255,255,.10);
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg-btn:hover{ background: rgba(255,255,255,.06); }
|
||||||
|
.pg-btn.is-disabled{ opacity: .45; }
|
||||||
|
|
||||||
|
.pg-pages{ display:flex; gap:6px; align-items:center; }
|
||||||
|
.pg-page, .pg-ellipsis{
|
||||||
|
min-width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-decoration:none;
|
||||||
|
border: 1px solid rgba(255,255,255,.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg-page:hover{ background: rgba(255,255,255,.06); }
|
||||||
|
.pg-page.is-active{
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
border-color: rgba(255,255,255,.18);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-head-row{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content: space-between; /* ✅ 양끝 배치 */
|
||||||
|
gap:12px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,.08); /* ✅ 라이트 배경 기준: 검정 계열 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 다크 테마일 때만 흰색 라인으로(선택) */
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.notice-head-row{
|
||||||
|
border-bottom-color: rgba(255,255,255,.10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-count{
|
||||||
|
flex: 0 0 auto; /* ✅ 줄어들지 않게 */
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-toolbar{
|
||||||
|
margin-left: auto; /* ✅ 오른쪽으로 밀기(보험) */
|
||||||
|
display:flex;
|
||||||
|
justify-content:flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-toolbar .nt-right{
|
||||||
|
display:flex;
|
||||||
|
justify-content:flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nt-search{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:8px;
|
||||||
|
flex-wrap: nowrap; /* ✅ 검색 영역도 한 줄 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 검색 input 폭이 너무 커서 내려가는 걸 방지 */
|
||||||
|
.nt-search input{
|
||||||
|
width: clamp(180px, 28vw, 260px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 아주 좁은 화면에서만 2줄 허용(선택) */
|
||||||
|
@media (max-width: 380px){
|
||||||
|
.notice-head-row{ flex-wrap: wrap; }
|
||||||
|
.notice-toolbar{ margin-left: 0; width: 100%; }
|
||||||
|
.notice-toolbar .nt-right{ justify-content:flex-end; width:100%; }
|
||||||
|
.nt-search input{ width: 100%; }
|
||||||
|
}
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.notice-count{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-head-row{
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-toolbar{
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-toolbar .nt-right{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nt-search{
|
||||||
|
width: 100%;
|
||||||
|
display:flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ✅ 핵심: 기존 width:240px 죽이고, flex로 꽉 채우기 */
|
||||||
|
.nt-search input{
|
||||||
|
width: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nt-search button{
|
||||||
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== notice view upgrades ===== */
|
||||||
|
|
||||||
|
.nv-top{
|
||||||
|
display:flex;
|
||||||
|
justify-content:flex-end;
|
||||||
|
margin: 6px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-btn{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(0,0,0,.08);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-btn--ghost:hover{
|
||||||
|
background: rgba(0,0,0,.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 제목 크기 조금 줄이기 */
|
||||||
|
.nv-title--sm{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 내용 박스 라인 */
|
||||||
|
.nv-content-box{
|
||||||
|
padding: 14px 16px 16px;
|
||||||
|
border-top: 1px solid rgba(0,0,0,.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.nv-btn{ border-color: rgba(255,255,255,.10); }
|
||||||
|
.nv-btn--ghost:hover{ background: rgba(255,255,255,.06); }
|
||||||
|
.nv-content-box{ border-top-color: rgba(255,255,255,.06); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 첨부영역(내용 박스 안) */
|
||||||
|
.nv-attach--inbox{
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid rgba(0,0,0,.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.nv-attach--inbox{ border-top-color: rgba(255,255,255,.06); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-attach-list{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-attach-item{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
gap:12px;
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(0,0,0,.08);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-attach-item:hover{
|
||||||
|
background: rgba(0,0,0,.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.nv-attach-item{
|
||||||
|
border-color: rgba(255,255,255,.10);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
}
|
||||||
|
.nv-attach-item:hover{
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-attach-name{
|
||||||
|
flex:1;
|
||||||
|
overflow:hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
font-size: 13px;
|
||||||
|
opacity:.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-attach-action{
|
||||||
|
flex:0 0 auto;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity:.75;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(0,0,0,.08);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.nv-attach-action{
|
||||||
|
border-color: rgba(255,255,255,.10);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 하단 버튼(이전/목록/다음) */
|
||||||
|
.nv-actions{
|
||||||
|
margin-top: 16px;
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
gap:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action{
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(0,0,0,.08);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
text-decoration:none;
|
||||||
|
padding: 12px 12px;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:6px;
|
||||||
|
min-height: 62px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action:hover{
|
||||||
|
background: rgba(0,0,0,.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
.nv-action{
|
||||||
|
border-color: rgba(255,255,255,.10);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
}
|
||||||
|
.nv-action:hover{
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action--list{
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
padding: 12px 14px;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action--disabled{
|
||||||
|
opacity: .55;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action-top{
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
opacity:.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-action-title{
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.35;
|
||||||
|
overflow:hidden;
|
||||||
|
display:-webkit-box;
|
||||||
|
-webkit-line-clamp: 2; /* ✅ 두 줄 */
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 모바일: 1열로 */
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.nv-actions{
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.nv-action--list{
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== nv-head: desktop 1-line / mobile 2-lines ===== */
|
||||||
|
.nv-head{
|
||||||
|
display:flex;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:space-between; /* ✅ 웹: 한 줄 양끝 */
|
||||||
|
gap:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-title{
|
||||||
|
margin:0;
|
||||||
|
min-width:0;
|
||||||
|
flex:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-meta{
|
||||||
|
margin-top: 0; /* ✅ 웹: 제목과 같은 줄 */
|
||||||
|
white-space: nowrap; /* ✅ 메타 줄바꿈 방지 */
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 모바일: 두 줄(제목 -> 메타) */
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.nv-head{
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.nv-meta{
|
||||||
|
margin-top: 8px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== bottom nav: prev/next only, web only ===== */
|
||||||
|
.nv-actions{
|
||||||
|
margin-top: 16px;
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns: 1fr 1fr; /* ✅ 이전/다음 2칸 */
|
||||||
|
gap:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 웹에서만 보이게 */
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.nv-actions--webonly{
|
||||||
|
display:none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 521px){
|
||||||
|
.nv-title{
|
||||||
|
overflow:hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -13,3 +13,4 @@
|
|||||||
body {
|
body {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
FF
|
||||||
BIN
public/assets/images/common/hero/hero-01-shop.png
Normal file
|
After Width: | Height: | Size: 6.7 MiB |
BIN
public/assets/images/common/hero/hero-01-shop.webp
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
public/assets/images/common/hero/hero-02-pay.png
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
public/assets/images/common/hero/hero-02-pay.webp
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
public/assets/images/common/hero/hero-03-support.png
Normal file
|
After Width: | Height: | Size: 6.3 MiB |
BIN
public/assets/images/common/hero/hero-03-support.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
public/assets/images/common/hero/hero-04-special.png
Normal file
|
After Width: | Height: | Size: 6.4 MiB |
BIN
public/assets/images/common/hero/hero-04-special.webp
Normal file
|
After Width: | Height: | Size: 115 KiB |
33
public/assets/js/cs-notice.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// cs-notice.js
|
||||||
|
(function () {
|
||||||
|
const list = document.querySelector('.notice-list2');
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
const q = (list.getAttribute('data-highlight') || '').trim();
|
||||||
|
if (!q) return;
|
||||||
|
|
||||||
|
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
const re = new RegExp(escapeRegExp(q), 'ig');
|
||||||
|
|
||||||
|
document.querySelectorAll('.js-hl').forEach((el) => {
|
||||||
|
const text = el.textContent || '';
|
||||||
|
if (!re.test(text)) return;
|
||||||
|
|
||||||
|
// 안전: textContent 기반으로 split -> mark
|
||||||
|
const parts = text.split(new RegExp(`(${escapeRegExp(q)})`, 'ig'));
|
||||||
|
el.textContent = '';
|
||||||
|
parts.forEach((p) => {
|
||||||
|
if (p.toLowerCase() === q.toLowerCase()) {
|
||||||
|
const mark = document.createElement('mark');
|
||||||
|
mark.textContent = p;
|
||||||
|
mark.style.background = 'rgba(255,255,255,.14)';
|
||||||
|
mark.style.color = 'inherit';
|
||||||
|
mark.style.padding = '0 2px';
|
||||||
|
mark.style.borderRadius = '6px';
|
||||||
|
el.appendChild(mark);
|
||||||
|
} else {
|
||||||
|
el.appendChild(document.createTextNode(p));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
@ -367,7 +367,7 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
.hero-slider {
|
.hero-slider {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 400px;
|
height: 280px;
|
||||||
background-color: var(--color-bg-section);
|
background-color: var(--color-bg-section);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1638,14 +1638,14 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
|
|
||||||
/* ===== Hero variants ===== */
|
/* ===== Hero variants ===== */
|
||||||
.hero-slider{ position: relative; overflow: hidden; background: var(--color-bg-section); }
|
.hero-slider{ position: relative; overflow: hidden; background: var(--color-bg-section); }
|
||||||
.hero-slider--main{ height: 400px; }
|
.hero-slider--main{ height: 280px; }
|
||||||
.hero-slider--compact{ height: 170px; border-radius: 16px; border: 1px solid #E5E7EB; margin: 14px 0 18px; }
|
.hero-slider--compact{ height: 280px; border-radius: 16px; border: 1px solid #E5E7EB; margin: 14px 0 18px; }
|
||||||
|
|
||||||
/* track/slide 동일 */
|
/* track/slide 동일 */
|
||||||
.hero-track{ display:flex; height:100%; transition: transform 0.5s cubic-bezier(0.25,1,0.5,1); }
|
.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-slide{ min-width:100%; height:100%; display:flex; align-items:center; justify-content:center; padding: 0 24px; position: relative;}
|
||||||
|
|
||||||
.hero-content{ max-width: var(--container-width); width:100%; text-align:left; padding: 0 8px; }
|
.hero-content{ max-width: var(--container-width); width:100%; text-align:left; padding: 0 8px; position: relative; z-index: 1;}
|
||||||
|
|
||||||
/* ✅ compact에서 타이포/여백만 줄이기 */
|
/* ✅ compact에서 타이포/여백만 줄이기 */
|
||||||
.hero-slider--compact .hero-slide{ padding: 0 18px; }
|
.hero-slider--compact .hero-slide{ padding: 0 18px; }
|
||||||
@ -1653,6 +1653,45 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
.hero-slider--compact .hero-desc{ font-size: 13px; margin-bottom: 10px; }
|
.hero-slider--compact .hero-desc{ font-size: 13px; margin-bottom: 10px; }
|
||||||
.hero-slider--compact .btn.hero-cta{ padding: 8px 14px; font-size: 13px; }
|
.hero-slider--compact .btn.hero-cta{ padding: 8px 14px; font-size: 13px; }
|
||||||
|
|
||||||
|
.hero-slide::before{
|
||||||
|
content:"";
|
||||||
|
position:absolute;
|
||||||
|
inset:0;
|
||||||
|
background-image: var(--hero-bg);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: var(--hero-bg-pos, center);
|
||||||
|
z-index:0;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px){
|
||||||
|
.hero-slide::before{
|
||||||
|
background-image: var(--hero-bg-mobile, var(--hero-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ✅ 텍스트 가독성 오버레이(왼쪽은 밝게, 오른쪽은 투명) */
|
||||||
|
.hero-slide::after{
|
||||||
|
content:"";
|
||||||
|
position:absolute;
|
||||||
|
inset:0;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
rgba(255,255,255,.92) 0%,
|
||||||
|
rgba(255,255,255,.80) 45%,
|
||||||
|
rgba(255,255,255,.00) 78%
|
||||||
|
);
|
||||||
|
z-index:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 모바일은 중앙 쪽까지 좀 더 밝게 */
|
||||||
|
@media (max-width: 768px){
|
||||||
|
.hero-slide::after{
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
rgba(255,255,255,.94) 0%,
|
||||||
|
rgba(255,255,255,.86) 60%,
|
||||||
|
rgba(255,255,255,.10) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ✅ 왼쪽/오른쪽 화살표 버튼이 컨텐츠를 가리지 않게 여백 확보 */
|
/* ✅ 왼쪽/오른쪽 화살표 버튼이 컨텐츠를 가리지 않게 여백 확보 */
|
||||||
.slider-arrow{
|
.slider-arrow{
|
||||||
position:absolute; top:50%; transform: translateY(-50%);
|
position:absolute; top:50%; transform: translateY(-50%);
|
||||||
@ -1676,7 +1715,7 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
.dot.active{ background: var(--color-accent-blue); transform: scale(1.15); }
|
.dot.active{ background: var(--color-accent-blue); transform: scale(1.15); }
|
||||||
|
|
||||||
@media (max-width: 768px){
|
@media (max-width: 768px){
|
||||||
.hero-slider--compact{ height: 150px; }
|
.hero-slider--compact{ height: 200px; }
|
||||||
.hero-slider--compact .hero-content{ padding-left: 14px; padding-right: 14px; } /* 모바일에선 화살표가 위에 떠도 괜찮게 */
|
.hero-slider--compact .hero-content{ padding-left: 14px; padding-right: 14px; } /* 모바일에선 화살표가 위에 떠도 괜찮게 */
|
||||||
.slider-arrow{ display:none; } /* 모바일은 스와이프/도트로만 */
|
.slider-arrow{ display:none; } /* 모바일은 스와이프/도트로만 */
|
||||||
}
|
}
|
||||||
@ -3919,3 +3958,84 @@ body.is-drawer-open{
|
|||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Pagination (badge / pill) ===== */
|
||||||
|
.notice-pager,
|
||||||
|
.pagination-wrap {
|
||||||
|
margin-top: 22px; /* ✅ 상단 여백 */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* ✅ 가운데 정렬 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
gap:8px;
|
||||||
|
padding: 0; /* ✅ 박스 제거 */
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pg-pages{
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 공통 배지 */
|
||||||
|
.pg-btn,
|
||||||
|
.pg-page,
|
||||||
|
.pg-ellipsis{
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 999px; /* ✅ pill */
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
text-decoration:none;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
user-select:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 숫자 배지 */
|
||||||
|
.pg-page{
|
||||||
|
min-width: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hover */
|
||||||
|
.pg-btn:hover,
|
||||||
|
.pg-page:hover{
|
||||||
|
background: rgba(255,255,255,.10);
|
||||||
|
border-color: rgba(255,255,255,.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active */
|
||||||
|
.pg-page.is-active{
|
||||||
|
background: rgba(255,255,255,.16);
|
||||||
|
border-color: rgba(255,255,255,.22);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disabled */
|
||||||
|
.pg-btn.is-disabled{
|
||||||
|
opacity: .38;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ellipsis */
|
||||||
|
.pg-ellipsis{
|
||||||
|
opacity:.55;
|
||||||
|
border-style: dashed;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mobile */
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.notice-pager,
|
||||||
|
.pagination-wrap { margin-top: 18px; }
|
||||||
|
.pg-btn, .pg-page, .pg-ellipsis { height: 32px; font-size: 12px; }
|
||||||
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<!-- Center: Search (Desktop) -->
|
<!-- Center: Search (Desktop) -->
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<form action="/shop" method="GET">
|
<form action="/shop" method="GET">
|
||||||
<input type="text" name="search" class="search-input" placeholder="상품권/브랜드 검색 (예: 문상, 해피, 구글플레이)">
|
<input type="text" name="search" class="search-input" placeholder="상품권/브랜드 검색">
|
||||||
<button type="submit" class="search-icon" aria-label="검색">
|
<button type="submit" class="search-icon" aria-label="검색">
|
||||||
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
['label' => '공지사항', 'url' => url()->current()],
|
['label' => '공지사항', 'url' => url()->current()],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ✅ 이것만 남기면 됨 (탭 목록은 config에서 자동)
|
|
||||||
$csActive = 'notice';
|
$csActive = 'notice';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@ -18,45 +17,69 @@
|
|||||||
@section('meta_description', 'PIN FOR YOU 공지사항입니다. 운영/결제/발송 관련 최신 안내를 확인하세요.')
|
@section('meta_description', 'PIN FOR YOU 공지사항입니다. 운영/결제/발송 관련 최신 안내를 확인하세요.')
|
||||||
@section('canonical', url('/cs/notice'))
|
@section('canonical', url('/cs/notice'))
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link rel="stylesheet" href="{{ asset('assets/css/cs-notice.css') }}">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script defer src="{{ asset('assets/js/cs-notice.js') }}"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@section('subcontent')
|
@section('subcontent')
|
||||||
|
|
||||||
<div class="notice-page">
|
<div class="notice-page">
|
||||||
<ul class="notice-list2">
|
|
||||||
{{-- TODO: DB 연결 전 더미 --}}
|
|
||||||
<li><a href="/cs/notice/1"><span class="n2-tag">공지</span><span class="n2-text">설 연휴 고객센터 운영 안내</span><time class="n2-date">2026.01.09</time></a></li>
|
|
||||||
<li><a href="/cs/notice/2"><span class="n2-tag n2-tag--info">안내</span><span class="n2-text">휴대폰 결제 점검 안내(일시)</span><time class="n2-date">2026.01.08</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내핀번호 발송/재발송 정책 안내핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">발송</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">경고</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">당첨자</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
<li><a href="/cs/notice/3"><span class="n2-tag n2-tag--info">안내</span><span class="n2s-text">핀번호 발송/재발송 정책 안내</span><time class="n2-date">2026.01.06</time></a></li>
|
|
||||||
|
|
||||||
|
<div class="notice-head-row">
|
||||||
|
<div class="notice-count">
|
||||||
|
총 <strong>{{ number_format($notices->total()) }}</strong>건
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notice-toolbar" role="search">
|
||||||
|
<form class="nt-right" action="{{ route('web.cs.notice.index') }}" method="GET">
|
||||||
|
<div class="nt-search">
|
||||||
|
<input type="text" name="q" value="{{ $q ?? request('q') }}"
|
||||||
|
placeholder="검색"
|
||||||
|
aria-label="공지 검색">
|
||||||
|
@if(($q ?? '') !== '')
|
||||||
|
<a class="nt-clear" href="{{ route('web.cs.notice.index') }}" aria-label="검색 초기화">×</a>
|
||||||
|
@endif
|
||||||
|
<button type="submit" aria-label="검색">검색</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="notice-list2" data-highlight="{{ $q ?? '' }}">
|
||||||
|
@forelse($notices as $row)
|
||||||
|
@php
|
||||||
|
$tag = $row->uiTag();
|
||||||
|
$dt = $row->regdateCarbon();
|
||||||
|
@endphp
|
||||||
|
<li class="notice-item">
|
||||||
|
<a href="{{ route('web.cs.notice.show', ['seq' => $row->seq]) }}" class="notice-link">
|
||||||
|
<span class="{{ $tag['class'] }}">{{ $tag['label'] }}</span>
|
||||||
|
<span class="n2-text js-hl">{{ $row->subject }}</span>
|
||||||
|
<time class="n2-date">{{ $dt ? $dt->format('Y.m.d') : '-' }}</time>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@empty
|
||||||
|
<li class="notice-empty">
|
||||||
|
<div class="notice-empty-box">
|
||||||
|
<strong>공지사항이 없습니다.</strong>
|
||||||
|
@if(($q ?? '') !== '')
|
||||||
|
<div class="notice-empty-sub">다른 검색어로 다시 시도해 주세요.</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endforelse
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{-- Notice Toolbar (replace sub-quick) --}}
|
@if($notices->hasPages())
|
||||||
<div class="notice-toolbar" role="search">
|
<div class="notice-pager">
|
||||||
<form class="nt-right" action="{{ url('/cs/notice') }}" method="GET">
|
{{ $notices->links('web.partials.pagination') }}
|
||||||
<div class="nt-search">
|
</div>
|
||||||
<input type="text" name="q" value="{{ request('q') }}"
|
@endif
|
||||||
placeholder="검색"
|
|
||||||
aria-label="공지 검색">
|
|
||||||
<button type="submit" aria-label="검색">검색</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@include('web.partials.cs-quick-actions')
|
@include('web.partials.cs-quick-actions')
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|||||||
146
resources/views/web/cs/notice/show.blade.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
@php
|
||||||
|
$pageTitle = '공지사항';
|
||||||
|
$pageDesc = '서비스 운영 및 결제/발송 관련 안내를 확인하세요.';
|
||||||
|
|
||||||
|
$breadcrumbs = [
|
||||||
|
['label' => '홈', 'url' => url('/')],
|
||||||
|
['label' => '고객센터', 'url' => url('/cs')],
|
||||||
|
['label' => '공지사항', 'url' => url('/cs/notice')],
|
||||||
|
['label' => $notice->subject, 'url' => url()->current()],
|
||||||
|
];
|
||||||
|
|
||||||
|
$csActive = 'notice';
|
||||||
|
$dt = $notice->regdateCarbon();
|
||||||
|
|
||||||
|
// 첨부파일명 추출(경로/URL 모두 대응)
|
||||||
|
$file1 = $notice->file_01 ? basename(parse_url($notice->file_01, PHP_URL_PATH) ?? $notice->file_01) : null;
|
||||||
|
$file2 = $notice->file_02 ? basename(parse_url($notice->file_02, PHP_URL_PATH) ?? $notice->file_02) : null;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@extends('web.layouts.subpage')
|
||||||
|
|
||||||
|
@section('title', e($notice->subject) . ' | 공지사항 | PIN FOR YOU')
|
||||||
|
@section('meta_description', 'PIN FOR YOU 공지사항 상세 페이지입니다.')
|
||||||
|
@section('canonical', url('/cs/notice/'.$notice->seq))
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link rel="stylesheet" href="{{ asset('assets/css/cs-notice.css') }}">
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('subcontent')
|
||||||
|
<div class="notice-view">
|
||||||
|
|
||||||
|
{{-- 상단: 목록보기 버튼 --}}
|
||||||
|
<div class="nv-top">
|
||||||
|
<a class="nv-btn nv-btn--ghost"
|
||||||
|
href="{{ route('web.cs.notice.index', request()->only('q','page')) }}">
|
||||||
|
목록보기
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="nv-card">
|
||||||
|
<header class="nv-head">
|
||||||
|
<h2 class="nv-title nv-title--sm">{{ $notice->subject }}</h2>
|
||||||
|
<div class="nv-meta">
|
||||||
|
<time>{{ $dt ? $dt->format('Y.m.d H:i') : '-' }}</time>
|
||||||
|
<span class="nv-dot">·</span>
|
||||||
|
<span>조회 {{ number_format((int)$notice->hit) }}</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{{-- ✅ 내용 박스 라인 + 내부 패딩 --}}
|
||||||
|
<div class="nv-content-box">
|
||||||
|
<div class="nv-body editor-content">
|
||||||
|
{!! $notice->content !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- 첨부/링크 --}}
|
||||||
|
@php
|
||||||
|
$link1 = $notice->safeLink($notice->link_01);
|
||||||
|
$link2 = $notice->safeLink($notice->link_02);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($link1 || $link2 || $notice->file_01 || $notice->file_02)
|
||||||
|
<div class="nv-attach nv-attach--inbox">
|
||||||
|
<div class="nv-attach-title">첨부 / 관련 링크</div>
|
||||||
|
|
||||||
|
<div class="nv-attach-list">
|
||||||
|
|
||||||
|
@if($notice->file_01)
|
||||||
|
<a class="nv-attach-item"
|
||||||
|
href="{{ $notice->file_01 }}"
|
||||||
|
download="{{ $file1 ?? '' }}">
|
||||||
|
<span class="nv-attach-name">{{ $file1 }}</span>
|
||||||
|
<span class="nv-attach-action">다운로드</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($notice->file_02)
|
||||||
|
<a class="nv-attach-item"
|
||||||
|
href="{{ $notice->file_02 }}"
|
||||||
|
download="{{ $file2 ?? '' }}">
|
||||||
|
<span class="nv-attach-name">{{ $file2 }}</span>
|
||||||
|
<span class="nv-attach-action">다운로드</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($link1)
|
||||||
|
<a class="nv-attach-item"
|
||||||
|
href="{{ $link1 }}"
|
||||||
|
target="_blank" rel="noopener">
|
||||||
|
<span class="nv-attach-name">관련 링크 1</span>
|
||||||
|
<span class="nv-attach-action">열기</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($link2)
|
||||||
|
<a class="nv-attach-item"
|
||||||
|
href="{{ $link2 }}"
|
||||||
|
target="_blank" rel="noopener">
|
||||||
|
<span class="nv-attach-name">관련 링크 2</span>
|
||||||
|
<span class="nv-attach-action">열기</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- ✅ 박스 안에서 하단 네비(이전/다음/목록) --}}
|
||||||
|
<nav class="nv-actions nv-actions--webonly" aria-label="공지 네비게이션">
|
||||||
|
@if($prev)
|
||||||
|
<a class="nv-action nv-action--prev"
|
||||||
|
href="{{ route('web.cs.notice.show', ['seq' => $prev->seq]) }}">
|
||||||
|
<div class="nv-action-top">이전</div>
|
||||||
|
<div class="nv-action-title">{{ $prev->subject }}</div>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<div class="nv-action nv-action--disabled" aria-disabled="true">
|
||||||
|
<div class="nv-action-top">이전</div>
|
||||||
|
<div class="nv-action-title">없음</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($next)
|
||||||
|
<a class="nv-action nv-action--next"
|
||||||
|
href="{{ route('web.cs.notice.show', ['seq' => $next->seq]) }}">
|
||||||
|
<div class="nv-action-top">다음</div>
|
||||||
|
<div class="nv-action-title">{{ $next->subject }}</div>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<div class="nv-action nv-action--disabled" aria-disabled="true">
|
||||||
|
<div class="nv-action-top">다음</div>
|
||||||
|
<div class="nv-action-title">없음</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@include('web.partials.cs-quick-actions')
|
||||||
|
@endsection
|
||||||
|
|
||||||
@ -40,12 +40,13 @@
|
|||||||
{{-- <meta name="keywords" content="..."> --}}
|
{{-- <meta name="keywords" content="..."> --}}
|
||||||
|
|
||||||
{{-- Font --}}
|
{{-- Font --}}
|
||||||
<link rel="stylesheet" href="{{ asset('css/pretendard.css') }}">
|
<link rel="stylesheet" href="{{ asset('assets/css/pretendard.css') }}">
|
||||||
<link rel="preload"
|
<link rel="preload"
|
||||||
href="{{ asset('assets/fonts/pretendard/PretendardVariable.woff2') }}"
|
href="{{ asset('assets/fonts/pretendard/PretendardVariable.woff2') }}"
|
||||||
as="font" type="font/woff2" crossorigin>
|
as="font" type="font/woff2" crossorigin>
|
||||||
|
|
||||||
{{-- Web Bundle --}}
|
{{-- Web Bundle --}}
|
||||||
|
@stack('styles')
|
||||||
@vite(['resources/css/web.css', 'resources/js/web.js'])
|
@vite(['resources/css/web.css', 'resources/js/web.js'])
|
||||||
|
|
||||||
{{-- 페이지별 추가 head --}}
|
{{-- 페이지별 추가 head --}}
|
||||||
|
|||||||
@ -3,26 +3,30 @@
|
|||||||
'variant' => 'compact', // 'main' | 'compact'
|
'variant' => 'compact', // 'main' | 'compact'
|
||||||
'id' => 'hero'
|
'id' => 'hero'
|
||||||
])
|
])
|
||||||
|
|
||||||
@if(!empty($slides))
|
@if(!empty($slides))
|
||||||
<section class="hero-slider hero-slider--{{ $variant }}" id="{{ $id }}" aria-label="프로모션 배너">
|
<section class="hero-slider hero-slider--{{ $variant }}" id="{{ $id }}" aria-label="프로모션 배너">
|
||||||
<div class="hero-track">
|
<div class="hero-track">
|
||||||
@foreach($slides as $s)
|
@foreach($slides as $s)
|
||||||
<div class="hero-slide">
|
@php
|
||||||
|
$img = $s['image'] ?? null;
|
||||||
|
$imgDefault = is_array($img) ? ($img['default'] ?? null) : $img;
|
||||||
|
$imgMobile = is_array($img) ? ($img['mobile'] ?? null) : null;
|
||||||
|
$bgPos = $s['bg_pos'] ?? 'center';
|
||||||
|
$style = '';
|
||||||
|
if ($imgDefault) $style .= "--hero-bg:url('{$imgDefault}');";
|
||||||
|
if ($imgMobile) $style .= "--hero-bg-mobile:url('{$imgMobile}');";
|
||||||
|
if ($bgPos) $style .= "--hero-bg-pos:{$bgPos};";
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="hero-slide" style="{{ $style }}">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
@if(!empty($s['kicker']))
|
@if(!empty($s['kicker'])) <div class="hero-kicker">{{ $s['kicker'] }}</div> @endif
|
||||||
<div class="hero-kicker">{{ $s['kicker'] }}</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<h2 class="hero-title">{{ $s['title'] }}</h2>
|
<h2 class="hero-title">{{ $s['title'] }}</h2>
|
||||||
|
@if(!empty($s['desc'])) <p class="hero-desc">{{ $s['desc'] }}</p> @endif
|
||||||
@if(!empty($s['desc']))
|
|
||||||
<p class="hero-desc">{{ $s['desc'] }}</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if(!empty($s['cta_label']) && !empty($s['cta_url']))
|
@if(!empty($s['cta_label']) && !empty($s['cta_url']))
|
||||||
<a href="{{ $s['cta_url'] }}" class="btn btn-primary hero-cta">
|
<a href="{{ $s['cta_url'] }}" class="btn btn-primary hero-cta">{{ $s['cta_label'] }}</a>
|
||||||
{{ $s['cta_label'] }}
|
|
||||||
</a>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +36,6 @@
|
|||||||
@if(count($slides) > 1)
|
@if(count($slides) > 1)
|
||||||
<button class="slider-arrow prev" aria-label="이전 배너">‹</button>
|
<button class="slider-arrow prev" aria-label="이전 배너">‹</button>
|
||||||
<button class="slider-arrow next" aria-label="다음 배너">›</button>
|
<button class="slider-arrow next" aria-label="다음 배너">›</button>
|
||||||
|
|
||||||
<div class="dots-container" aria-label="배너 선택">
|
<div class="dots-container" aria-label="배너 선택">
|
||||||
@foreach($slides as $idx => $s)
|
@foreach($slides as $idx => $s)
|
||||||
<button class="dot {{ $idx===0?'active':'' }}" data-index="{{ $idx }}" aria-label="배너 {{ $idx+1 }}"></button>
|
<button class="dot {{ $idx===0?'active':'' }}" data-index="{{ $idx }}" aria-label="배너 {{ $idx+1 }}"></button>
|
||||||
|
|||||||
36
resources/views/web/partials/pagination.blade.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav class="pg" role="navigation" aria-label="페이지 네비게이션">
|
||||||
|
{{-- Prev --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<span class="pg-btn is-disabled" aria-disabled="true">이전</span>
|
||||||
|
@else
|
||||||
|
<a class="pg-btn" href="{{ $paginator->previousPageUrl() }}" rel="prev">이전</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pages --}}
|
||||||
|
<div class="pg-pages">
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
@if (is_string($element))
|
||||||
|
<span class="pg-ellipsis">{{ $element }}</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<span class="pg-page is-active" aria-current="page">{{ $page }}</span>
|
||||||
|
@else
|
||||||
|
<a class="pg-page" href="{{ $url }}">{{ $page }}</a>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Next --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<a class="pg-btn" href="{{ $paginator->nextPageUrl() }}" rel="next">다음</a>
|
||||||
|
@else
|
||||||
|
<span class="pg-btn is-disabled" aria-disabled="true">다음</span>
|
||||||
|
@endif
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Route;
|
|||||||
|
|
||||||
use App\Http\Controllers\Web\Auth\FindIdController;
|
use App\Http\Controllers\Web\Auth\FindIdController;
|
||||||
use App\Http\Controllers\Web\Auth\FindPasswordController;
|
use App\Http\Controllers\Web\Auth\FindPasswordController;
|
||||||
|
use App\Http\Controllers\Web\Cs\NoticeController;
|
||||||
|
|
||||||
Route::view('/', 'web.home')->name('web.home');
|
Route::view('/', 'web.home')->name('web.home');
|
||||||
|
|
||||||
@ -13,7 +14,8 @@ Route::view('/', 'web.home')->name('web.home');
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
Route::prefix('cs')->name('web.cs.')->group(function () {
|
Route::prefix('cs')->name('web.cs.')->group(function () {
|
||||||
Route::view('notice', 'web.cs.notice.index')->name('notice.index');
|
Route::get('/notice', [NoticeController::class, 'index'])->name('notice.index');
|
||||||
|
Route::get('/notice/{seq}', [NoticeController::class, 'show'])->whereNumber('seq')->name('notice.show');
|
||||||
Route::view('faq', 'web.cs.faq.index')->name('faq.index');
|
Route::view('faq', 'web.cs.faq.index')->name('faq.index');
|
||||||
Route::view('kakao', 'web.cs.kakao.index')->name('kakao.index');
|
Route::view('kakao', 'web.cs.kakao.index')->name('kakao.index');
|
||||||
Route::view('qna', 'web.cs.qna.index')->name('qna.index');
|
Route::view('qna', 'web.cs.qna.index')->name('qna.index');
|
||||||
@ -77,5 +79,23 @@ Route::prefix('auth')->name('web.auth.')->group(function () {
|
|||||||
Route::get('/login', fn() => redirect()->route('web.auth.login'))->name('web.login');
|
Route::get('/login', fn() => redirect()->route('web.auth.login'))->name('web.login');
|
||||||
Route::get('/register', fn() => redirect()->route('web.auth.register'))->name('web.signup');
|
Route::get('/register', fn() => redirect()->route('web.auth.register'))->name('web.signup');
|
||||||
|
|
||||||
|
// Dev Lab (로컬에서만 + 파일 존재할 때만 라우트 등록)
|
||||||
|
if (app()->environment(['local', 'development', 'testing'])
|
||||||
|
&& class_exists(\App\Http\Controllers\Dev\DevLabController::class)) {
|
||||||
|
|
||||||
|
Route::prefix('__dev')->name('dev.')->group(function () {
|
||||||
|
Route::get('/lab', [\App\Http\Controllers\Dev\DevLabController::class, 'index'])->name('lab');
|
||||||
|
|
||||||
|
Route::post('/lab/mail', [\App\Http\Controllers\Dev\DevLabController::class, 'mail'])->name('lab.mail');
|
||||||
|
Route::post('/lab/mail-raw', [\App\Http\Controllers\Dev\DevLabController::class, 'mailRaw'])->name('lab.mail_raw');
|
||||||
|
Route::post('/lab/sms', [\App\Http\Controllers\Dev\DevLabController::class, 'sms'])->name('lab.sms');
|
||||||
|
Route::post('/lab/db', [\App\Http\Controllers\Dev\DevLabController::class, 'db'])->name('lab.db');
|
||||||
|
|
||||||
|
Route::post('/lab/session/set', [\App\Http\Controllers\Dev\DevLabController::class, 'sessionSet'])->name('lab.session.set');
|
||||||
|
Route::post('/lab/session/get', [\App\Http\Controllers\Dev\DevLabController::class, 'sessionGet'])->name('lab.session.get');
|
||||||
|
Route::post('/lab/session/forget', [\App\Http\Controllers\Dev\DevLabController::class, 'sessionForget'])->name('lab.session.forget');
|
||||||
|
Route::post('/lab/session/flush', [\App\Http\Controllers\Dev\DevLabController::class, 'sessionFlush'])->name('lab.session.flush');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Route::fallback(fn () => abort(404));
|
Route::fallback(fn () => abort(404));
|
||||||
|
|||||||