feat: 쇼핑몰 샘플 추가, 홈페이지 서비스 페이지 재디자인, 공통 UI 개선

- 모든 샘플 페이지 우측 하단 맨 위로 스크롤 버튼 추가 (인테리어, 독서)
- 독서 기록 노트 상단 '홈페이지 제작 서비스로 돌아가기' 배너 추가
- 개인 쇼핑몰 샘플 (MELLOW STUDIO) 신규 생성
  - 베이지/크림 라이트 톤, Cormorant Garamond + Pretendard
  - 히어로 스플릿 레이아웃, 상품 그리드(카테고리 필터), 브랜드 스토리, 리뷰, CTA, 푸터
  - 장바구니 뱃지, 상품 찜하기, 퀵 장바구니 인터랙션
- 홈페이지 서비스 소개 페이지 재디자인
  - CookieRun → Pretendard 교체로 한글 폰트 렌더링 개선
  - word-break: keep-all 적용으로 이상한 개행 제거
  - IntersectionObserver 스크롤 reveal 애니메이션 추가
  - Trust badge 섹션, Marquee 추가
  - 쇼핑몰 샘플 카드 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 17:07:56 +09:00
parent 0a907b4bfe
commit 53e01fad4a
4 changed files with 1429 additions and 555 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
/* ═══════════════════════════════════════
@@ -167,14 +168,25 @@ function BookCard({ book, large = false }: { book: Book; large?: boolean }) {
═══════════════════════════════════════ */
export default function ReadingPage() {
const [activeGenre, setActiveGenre] = useState('전체');
const [showTop, setShowTop] = useState(false);
useEffect(() => {
const scroller: HTMLElement =
(document.querySelector('.main-content') as HTMLElement | null) ??
document.documentElement;
const onScroll = () => setShowTop(scroller.scrollTop > 400);
scroller.addEventListener('scroll', onScroll, { passive: true });
const obs = new IntersectionObserver(
entries => entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('rd-visible'); }),
{ threshold: 0.1 }
{ threshold: 0.1, root: scroller === document.documentElement ? null : scroller }
);
document.querySelectorAll('.rd-reveal').forEach(el => obs.observe(el));
return () => obs.disconnect();
return () => {
scroller.removeEventListener('scroll', onScroll);
obs.disconnect();
};
}, []);
const genres = ['전체', '소설', '고전', '자기계발', '과학', '심리학'];
@@ -304,6 +316,24 @@ export default function ReadingPage() {
}
`}} />
{/* ── BACK BANNER ── */}
<div style={{
background: 'linear-gradient(135deg,#0C0B09,#1A1710)',
borderBottom: '1px solid rgba(212,168,83,0.12)',
height: 40, display: 'flex', alignItems: 'center',
padding: '0 24px', gap: 12, flexShrink: 0, position: 'relative', zIndex: 300,
}}>
<Link href="/services/website" style={{ color: 'rgba(212,168,83,0.7)', fontSize: 13, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, transition: 'color 0.2s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = '#D4A853')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = 'rgba(212,168,83,0.7)')}>
</Link>
<span style={{ color: 'rgba(212,168,83,0.2)' }}>|</span>
<span style={{ color: 'rgba(212,168,83,0.4)', fontSize: 12, fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic' }}>
SAMPLE ·
</span>
</div>
<div className="rd-root rd-grain" style={{ position: 'relative' }}>
{/* ── NAV ── */}
@@ -604,6 +634,30 @@ export default function ReadingPage() {
<p style={{ fontSize: '0.75rem', color: '#4A4035', margin: 0 }}> .</p>
</footer>
</div>
{/* ── SCROLL TO TOP ── */}
<button
onClick={() => {
const scroller = (document.querySelector('.main-content') as HTMLElement | null) ?? document.documentElement;
scroller.scrollTo({ top: 0, behavior: 'smooth' });
}}
style={{
position: 'fixed', bottom: '2rem', right: '2rem', zIndex: 400,
width: 48, height: 48, borderRadius: '50%',
background: '#D4A853', border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 8px 32px rgba(212,168,83,0.4)',
opacity: showTop ? 1 : 0,
transform: showTop ? 'translateY(0) scale(1)' : 'translateY(12px) scale(0.9)',
transition: 'opacity 0.35s cubic-bezier(0.16,1,0.3,1), transform 0.35s cubic-bezier(0.16,1,0.3,1)',
pointerEvents: showTop ? 'auto' : 'none',
}}
aria-label="맨 위로"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#0C0B09" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="18 15 12 9 6 15"/>
</svg>
</button>
</>
);
}