refactor(routes): 원본 25 파일 삭제 — Phase B에서 컨텐츠 이동 완료

Phase D 마무리:
- app/services/music/* (page, layout, samples — 3 파일)
- app/studio/page.tsx (1)
- app/freelance/* (page, layout — 2)
- app/services/website/* (page, layout, 8 samples — 10)
- app/services/blog/* (page, layout — 2)
- app/saju/* (page, layout, input, result, 2 sections, SajuForm — 7)

총 25 파일. next.config.ts redirects()로 외부 링크 보존 (영구 301).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 03:53:34 +09:00
parent e60749f21d
commit 972bfd8f8a
25 changed files with 0 additions and 9124 deletions

View File

@@ -1,27 +0,0 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '외주 개발 의뢰',
description:
'계약서 먼저, 납기 지키고, 소스코드 100% 인도. 47건 납품 완료. 현직 실무 엔지니어에게 외주 개발을 맡겨보세요. 납기 지연 시 하루 10만 원 패널티.',
keywords: [
'외주 개발',
'프리랜서 개발자',
'웹 개발 외주',
'앱 개발 외주',
'RPA 개발',
'업무 자동화 외주',
'소프트웨어 개발',
],
openGraph: {
title: '외주 개발 의뢰 | 쟁승메이드',
description:
'47건 납품 완료. 계약서 먼저, 납기 패널티, 소스코드 100% 인도. 연락 두절 없는 개발자.',
url: 'https://jaengseung-made.com/freelance',
},
robots: { index: false, follow: false },
};
export default function FreelanceLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,711 +0,0 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import ContactForm from '../components/ContactForm';
/* ─── Data ─── */
const portfolio = [
{
title: '기업 브랜드 홈페이지',
category: '웹사이트 제작 · Next.js',
desc: '제조업체의 영업용 기업 소개 사이트. 서비스·연혁·팀 소개·문의 폼 포함. 모바일 반응형 및 SEO 최적화까지 포함하여 납품.',
result: '납품 후 B2B 영업 미팅 시 "홈페이지 보고 연락했다" 비율 증가',
tags: ['Next.js', 'Tailwind CSS', 'Vercel', 'SEO'],
status: '납품 완료',
statusType: 'done',
priceRange: '50~200만원',
accentColor: 'text-indigo-400',
accentBg: 'bg-[#0d0a2e]',
borderAccent: 'border-indigo-400/20',
},
{
title: 'Gmail 자동화 RPA',
category: 'RPA · 업무 자동화',
desc: '거래처 이메일 수신 시 자동 분류, 답장 초안 작성, 담당자 알림 전송하는 Gmail 자동화 시스템.',
result: '이메일 처리 시간 일 2시간 → 10분 (의뢰인 직접 확인)',
tags: ['Python', 'Gmail API', 'Google Apps Script'],
status: '납품 완료',
statusType: 'done',
priceRange: '30~150만원',
accentColor: 'text-red-400',
accentBg: 'bg-[#200a0a]',
borderAccent: 'border-red-400/20',
},
{
title: '쇼핑몰 가격 모니터링 봇',
category: '웹 스크래핑 · 알림 자동화',
desc: '경쟁사 쇼핑몰의 특정 상품 가격을 매일 모니터링하여 변동 시 텔레그램으로 즉시 알림.',
result: '경쟁사 10곳 · 상품 50개 매일 자동 추적, 수동 확인 0분',
tags: ['Python', 'Selenium', 'Telegram Bot'],
status: '납품 완료',
statusType: 'done',
priceRange: '30~150만원',
accentColor: 'text-violet-400',
accentBg: 'bg-[#0d0a2e]',
borderAccent: 'border-violet-400/20',
},
{
title: '영업 일보 자동화 시스템',
category: '엑셀 자동화 · 보고서 생성',
desc: '영업 데이터 엑셀 파일을 자동으로 집계하여 일별/주별/월별 영업 일보 PDF를 생성하고 이메일 발송.',
result: '보고서 작성 3시간 → 5분, 매일 09:00 자동 발송',
tags: ['Python', 'OpenPyXL', 'ReportLab'],
status: '납품 완료',
statusType: 'done',
priceRange: '30~150만원',
accentColor: 'text-cyan-400',
accentBg: 'bg-[#012030]',
borderAccent: 'border-cyan-400/20',
},
{
title: '부동산 공시지가 수집 시스템',
category: '공공 데이터 · API 연동',
desc: '국토교통부 공공 API를 통해 특정 지역 공시지가를 주기적으로 수집·저장하고 변동 알림 제공.',
result: '전국 3개 지역 공시지가 주 1회 자동 수집·변동 알림',
tags: ['Python', '공공데이터 API', 'PostgreSQL', 'Telegram'],
status: '납품 완료',
statusType: 'done',
priceRange: '30~150만원',
accentColor: 'text-blue-400',
accentBg: 'bg-[#04102b]',
borderAccent: 'border-blue-400/20',
},
];
const testimonials = [
{
name: '이서준',
role: '온라인 쇼핑몰 운영자',
project: '경쟁사 가격 모니터링 봇',
content: '경쟁사 10곳 가격을 매일 수동으로 확인했는데 이제 텔레그램으로 자동 알림 받습니다. 납기도 정확히 지켜주셨고, 완료 후에도 작은 수정 요청에 빠르게 응답해주셔서 믿음이 갔습니다.',
result: '가격 모니터링 시간 → 0분/일',
accentColor: 'bg-emerald-500',
borderColor: 'border-emerald-200',
tagColor: 'text-emerald-700 bg-emerald-50 border-emerald-200',
},
{
name: '박하은',
role: '스타트업 운영팀장',
project: 'Excel 보고서 자동화 시스템',
content: '매주 월요일 아침 2시간씩 쓰던 Excel 집계 작업을 자동화했습니다. 처음엔 반신반의했는데 계약서부터 작성해주셔서 진짜 전문가구나 싶었고, 결과물도 기대 이상이었습니다.',
result: '주간 보고 작업 2시간 → 5분',
accentColor: 'bg-blue-500',
borderColor: 'border-blue-200',
tagColor: 'text-blue-700 bg-blue-50 border-blue-200',
},
{
name: '김도윤',
role: '프리랜서 디자이너',
project: '포트폴리오 웹사이트 제작',
content: '이전에 다른 개발자한테 맡겼다가 중간에 연락이 끊겼던 경험이 있어서 많이 걱정했는데, 주 1회 진행 보고를 꼬박꼬박 해주시고 최종 소스코드까지 전달해주셔서 정말 만족했습니다.',
result: '2주 납품 약속 정확히 이행',
accentColor: 'bg-violet-500',
borderColor: 'border-violet-200',
tagColor: 'text-violet-700 bg-violet-50 border-violet-200',
},
];
const process = [
{
num: '01',
title: '무료 상담',
desc: '전화 또는 이메일로 요구사항 파악 (30분 이내)',
sub: '비용 없음 · 부담 없음',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
),
},
{
num: '02',
title: '견적 제안',
desc: '개발 범위, 일정, 비용 상세 견적서 제공',
sub: '1~3일 이내 발송',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
},
{
num: '03',
title: '계약 체결',
desc: '계약서 작성 및 계약금(30%) 입금 후 개발 시작',
sub: '계약서 포함 · 안전 거래',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
),
},
{
num: '04',
title: '개발 진행',
desc: '주 1회 이상 진행 상황 공유 및 중간 검수',
sub: '투명한 진행 보고',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
highlight: true,
},
{
num: '05',
title: '최종 납품',
desc: '완성본 인도 + 사용 교육 + 소스코드 전달',
sub: '소스코드 전체 제공',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 13l4 4L19 7" />
</svg>
),
},
{
num: '06',
title: 'AS 지원',
desc: '1개월 무상 기술 지원 및 평생 유지보수 가능',
sub: '1개월 무상 + 평생 AS',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
),
},
];
const guarantees = [
{
label: '계약서 필수',
detail: '구두 약속 없음 — 착수 전 계약서 발송',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
accentText: 'text-sky-400',
accentBorder: 'border-sky-400/20',
},
{
label: '납기 지연 패널티',
detail: '하루 지연 = 10만원 감면 — 그래서 안 늦습니다',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
accentText: 'text-amber-400',
accentBorder: 'border-amber-400/20',
},
{
label: '소스코드 100% 인도',
detail: '납품 후 전체 소스코드 + 배포 가이드 제공',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
accentText: 'text-emerald-400',
accentBorder: 'border-emerald-400/20',
},
{
label: '1개월 무상 AS',
detail: '납품 후 한 달 — 버그·수정 무상 대응',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
),
accentText: 'text-violet-400',
accentBorder: 'border-violet-400/20',
},
{
label: '실시간 진행 현황',
detail: '마이페이지에서 7단계 진행 상황 직접 확인',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
),
accentText: 'text-cyan-400',
accentBorder: 'border-cyan-400/20',
},
];
/* ─── Scroll Reveal ─── */
function useScrollReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
);
el.querySelectorAll('.reveal').forEach((child) => observer.observe(child));
return () => observer.disconnect();
}, []);
return ref;
}
/* ─── Main Page ─── */
export default function FreelancePage() {
const [_contactPreset] = useState('');
const containerRef = useScrollReveal();
return (
<div ref={containerRef} className="min-h-full bg-[#f0f5ff]">
<style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
`}</style>
{/* ─── Hero ─── */}
<div
className="relative overflow-hidden bg-[#04102b] px-6 py-14 lg:px-12"
style={{ backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.012) 0px, rgba(255,255,255,0.012) 1px, transparent 1px, transparent 40px), repeating-linear-gradient(45deg, rgba(255,255,255,0.012) 0px, rgba(255,255,255,0.012) 1px, transparent 1px, transparent 40px)' }}
>
<div className="relative max-w-5xl mx-auto">
<div className="mb-10">
<div className="inline-flex items-center gap-2 bg-emerald-400/10 border border-emerald-400/20 text-emerald-300 text-xs font-semibold px-4 py-2 rounded-full mb-5">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
</div>
<h1 className="text-4xl md:text-5xl font-extrabold text-white tracking-tight leading-tight mb-4">
? .<br />
<span className="text-[#5ba4ff]"> , </span>
</h1>
<p className="text-blue-200/60 text-base md:text-lg max-w-xl leading-relaxed mb-2">
?<br />
, , .
</p>
</div>
{/* Developer tag */}
<div className="flex items-center gap-4 bg-white/5 border border-white/10 rounded-2xl px-6 py-3 mb-8 w-fit">
<div className="w-10 h-10 rounded-full bg-[#1a56db] flex items-center justify-center text-white font-extrabold text-sm flex-shrink-0">
</div>
<div>
<div className="text-white font-bold text-sm"> ()</div>
<div className="text-blue-300/50 text-xs"> · Python / Java / Next.js</div>
</div>
<div className="flex flex-wrap gap-2">
{['Python', 'Java', 'Next.js', 'Docker'].map(t => (
<span key={t} className="bg-[#1a56db]/20 border border-[#1a56db]/30 text-[#5ba4ff] text-xs px-2 py-0.5 rounded-md font-mono">{t}</span>
))}
</div>
</div>
{/* 보증 카드 4개 */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{guarantees.map((g) => (
<div key={g.label} className={`bg-[#04102b]/60 border ${g.accentBorder} rounded-xl p-4`}>
<div className={`${g.accentText} mb-2`}>{g.icon}</div>
<div className="text-white font-bold text-sm mb-1">{g.label}</div>
<div className="text-blue-300/40 text-xs leading-relaxed">{g.detail}</div>
</div>
))}
</div>
</div>
</div>
{/* ─── 포트폴리오 ─── */}
<div className="px-6 py-12 lg:px-12">
<div className="max-w-5xl mx-auto">
<div className="reveal text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">PORTFOLIO</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
<p className="text-slate-500 text-sm mt-2"> </p>
</div>
<div className="reveal grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{portfolio.map((item) => (
<div
key={item.title}
className="bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden hover:shadow-xl hover:shadow-blue-100 hover:-translate-y-1 transition-all duration-200 group"
>
{/* card header */}
<div className={`px-5 pt-5 pb-8 ${item.accentBg}`}>
<div className="flex items-start justify-between">
<div>
<div className={`text-xs font-bold mb-2 uppercase tracking-wider ${item.accentColor}`}>{item.category}</div>
<h3 className="text-white font-extrabold text-sm leading-snug">{item.title}</h3>
</div>
{item.statusType === 'live' ? (
<div className="flex items-center gap-1.5 bg-emerald-400/20 border border-emerald-400/30 text-emerald-300 text-xs font-bold px-2.5 py-1 rounded-full flex-shrink-0 ml-2">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
</div>
) : (
<div className="flex items-center gap-1.5 bg-blue-400/20 border border-blue-400/30 text-blue-300 text-xs font-bold px-2.5 py-1 rounded-full flex-shrink-0 ml-2">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
)}
</div>
</div>
{/* card body */}
<div className="px-5 py-4 -mt-3 relative">
<p className="text-slate-600 text-xs leading-relaxed mb-3">{item.desc}</p>
{item.result && (
<div className="flex items-start gap-1.5 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-2 mb-3">
<svg className="w-3.5 h-3.5 text-emerald-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-emerald-700 text-xs font-semibold leading-snug">{item.result}</span>
</div>
)}
<div className="flex flex-wrap gap-1.5">
{item.tags.map((tag) => (
<span key={tag} className="bg-[#f0f5ff] border border-[#dbe8ff] text-[#1a56db] text-xs font-mono px-2 py-0.5 rounded-md">
{tag}
</span>
))}
</div>
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
<span className="text-xs text-blue-600 font-semibold bg-blue-50 px-2 py-0.5 rounded-full">{item.priceRange}</span>
<a href="#contact-form" className="inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 font-medium transition">
</a>
</div>
</div>
</div>
))}
</div>
{/* 추가 문구 */}
<div className="reveal mt-6 text-center">
<p className="text-slate-400 text-sm">
·{' '}
<a href="mailto:bgg8988@gmail.com" className="text-[#1a56db] hover:underline font-medium"> </a>
</p>
</div>
</div>
</div>
{/* ─── 고객 후기 ─── */}
<div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto">
<div className="reveal text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">REVIEWS</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
<p className="text-slate-500 text-sm mt-2" style={{ wordBreak: 'keep-all' }}> </p>
</div>
<div className="reveal grid sm:grid-cols-2 md:grid-cols-3 gap-5">
{testimonials.map((t) => (
<div
key={t.name}
className={`bg-white rounded-2xl border-2 ${t.borderColor} p-6 flex flex-col hover:shadow-lg hover:-translate-y-0.5`}
style={{ transition: 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)' }}
>
{/* 별점 */}
<div className="flex items-center gap-0.5 mb-4">
{[1,2,3,4,5].map((n) => (
<svg key={n} className="w-4 h-4 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
))}
</div>
{/* 후기 내용 */}
<p className="text-slate-600 text-sm leading-relaxed flex-1 mb-5" style={{ wordBreak: 'keep-all' }}>
&ldquo;{t.content}&rdquo;
</p>
{/* 결과 뱃지 */}
<div className={`text-xs font-bold px-3 py-1.5 rounded-lg border mb-4 ${t.tagColor}`} style={{ wordBreak: 'keep-all' }}>
{t.result}
</div>
{/* 의뢰인 */}
<div className="flex items-center gap-3 pt-4 border-t border-slate-100">
<div className={`w-9 h-9 rounded-full ${t.accentColor} flex items-center justify-center text-white font-extrabold text-sm flex-shrink-0`}>
{t.name[0]}
</div>
<div>
<div className="font-bold text-[#04102b] text-sm">{t.name}</div>
<div className="text-slate-400 text-xs">{t.role} · {t.project}</div>
</div>
</div>
</div>
))}
</div>
<p className="text-center text-slate-400 text-xs mt-5">
* . .
</p>
<div className="reveal text-center py-6">
<a href="#contact-form" className="inline-flex items-center gap-2 px-6 py-3 bg-[#1a56db] text-white font-semibold rounded-xl hover:bg-blue-700 transition shadow-sm">
</a>
<p className="text-sm text-slate-400 mt-2">24 · </p>
</div>
</div>
</div>
{/* ─── 진행 프로세스 ─── */}
<div className="px-6 pb-12 lg:px-12">
<div className="max-w-3xl mx-auto">
<div className="reveal text-center mb-10">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">PROCESS</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
<p className="text-slate-500 text-sm mt-2"> 6 </p>
</div>
{/* Vertical timeline */}
<div className="reveal relative">
{/* connecting line */}
<div className="absolute left-6 top-6 bottom-6 w-px bg-[#dbe8ff]" />
<div className="space-y-4">
{process.map((p) => (
<div key={p.num} className="relative flex gap-5">
{/* step circle */}
<div className={`relative z-10 w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 shadow-lg ${
p.highlight
? 'bg-[#1a56db] shadow-blue-500/30 border border-[#1a56db]/50'
: 'bg-white border-2 border-[#dbe8ff]'
}`}>
<span className={p.highlight ? 'text-white' : 'text-[#1a56db]'}>{p.icon}</span>
</div>
{/* content */}
<div
className={`flex-1 rounded-2xl border p-5 mb-0 ${
p.highlight
? 'border-[#1a56db]/40'
: 'bg-white border-[#dbe8ff]'
}`}
style={p.highlight ? {
background: '#04102b',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)',
} : {}}
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className={`text-xs font-bold font-mono ${p.highlight ? 'text-[#5ba4ff]' : 'text-slate-400'}`}>STEP {p.num}</span>
{p.highlight && (
<span className="bg-[#1a56db]/30 border border-[#1a56db]/40 text-[#5ba4ff] text-xs font-bold px-2 py-0.5 rounded-md"> </span>
)}
</div>
<h3 className={`font-extrabold text-sm mb-1 ${p.highlight ? 'text-white' : 'text-[#04102b]'}`}>{p.title}</h3>
<p className={`text-xs leading-relaxed ${p.highlight ? 'text-blue-200/60' : 'text-slate-500'}`}>{p.desc}</p>
</div>
<div className={`text-xs font-semibold px-2.5 py-1 rounded-full whitespace-nowrap flex-shrink-0 ${
p.highlight
? 'bg-[#1a56db]/30 text-[#5ba4ff]'
: 'bg-[#f0f5ff] text-[#1a56db] border border-[#dbe8ff]'
}`}>
{p.sub}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
{/* ─── 기술 스택 & 신뢰 ─── */}
<div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto grid md:grid-cols-2 gap-5">
{/* Tech Stack */}
<div className="reveal reveal-d1 bg-white rounded-2xl border border-[#dbe8ff] p-6">
<div className="flex items-center gap-2 mb-4">
<div className="w-1 h-5 bg-[#1a56db] rounded-full" />
<h3 className="font-bold text-[#04102b] text-sm"> </h3>
</div>
<div className="space-y-3">
{[
{ label: 'Backend', techs: ['Python', 'Java', 'Spring Boot', 'FastAPI', 'Node.js'] },
{ label: 'Frontend', techs: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS'] },
{ label: 'Database', techs: ['PostgreSQL', 'MySQL', 'Redis', 'SQLite'] },
{ label: 'Infra / API', techs: ['Docker', 'AWS', 'Telegram API', '공공 API'] },
].map((group) => (
<div key={group.label}>
<div className="text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wider">{group.label}</div>
<div className="flex flex-wrap gap-1.5">
{group.techs.map((t) => (
<span key={t} className="bg-[#f0f5ff] border border-[#dbe8ff] text-[#1a56db] text-xs font-mono px-2.5 py-1 rounded-lg">
{t}
</span>
))}
</div>
</div>
))}
</div>
</div>
{/* 신뢰 포인트 */}
<div
className="reveal reveal-d2 rounded-2xl border border-[#1a3a7a] p-6"
style={{
background: '#04102b',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)',
}}
>
<div className="flex items-center gap-2 mb-4">
<div className="w-1 h-5 bg-[#5ba4ff] rounded-full" />
<h3 className="font-bold text-white text-sm"> </h3>
</div>
<ul className="space-y-3.5">
{[
{
title: '지금 URL로 직접 확인',
desc: 'jaengseung-made.com — 로또 분석, 주식 자동매매 지금도 운영 중',
icon: (
<svg className="w-4 h-4 text-[#5ba4ff] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
),
},
{
title: '계약서 먼저, 개발 나중',
desc: '구두 약속 없음 — 견적서·계약서 발송 후 착수',
icon: (
<svg className="w-4 h-4 text-[#5ba4ff] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
},
{
title: '납품 전 전액 환불 보장',
desc: '마음에 안 드시면 이유 불문 전액 환불',
icon: (
<svg className="w-4 h-4 text-[#5ba4ff] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
),
},
{
title: '소스코드 100% 인도',
desc: '완성 후 전체 소스코드 + 배포 가이드 제공',
icon: (
<svg className="w-4 h-4 text-[#5ba4ff] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
},
{
title: '납기 지연 시 패널티',
desc: '하루 늦을 때마다 10만원 감면 — 그래서 안 늦습니다',
icon: (
<svg className="w-4 h-4 text-[#5ba4ff] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
},
].map((item) => (
<li key={item.title} className="flex items-start gap-3">
{item.icon}
<div>
<div className="text-white text-sm font-bold">{item.title}</div>
<div className="text-blue-300/50 text-xs">{item.desc}</div>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
{/* ─── 문의 폼 ─── */}
<div id="contact-form" className="px-6 pb-14 lg:px-12">
<div className="max-w-5xl mx-auto">
<div className="reveal text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">CONTACT</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
<p className="text-slate-500 text-sm mt-2"> ? .</p>
</div>
<div className="reveal grid md:grid-cols-5 gap-6">
{/* 왼쪽: 간단 안내 */}
<div className="md:col-span-2 space-y-4">
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-5">
<h3 className="font-bold text-[#04102b] text-sm mb-4"> </h3>
<ul className="space-y-2.5">
{[
'어떤 업무를 자동화/개발하고 싶은지',
'현재 사용 중인 시스템 (엑셀, ERP 등)',
'희망하는 완성 일정',
'예산 범위 (대략적으로도 OK)',
].map((item, i) => (
<li key={item} className="flex items-start gap-2.5 text-xs text-slate-600">
<span className="w-5 h-5 rounded-full bg-[#f0f5ff] border border-[#dbe8ff] text-[#1a56db] font-bold text-xs flex items-center justify-center flex-shrink-0">{i + 1}</span>
{item}
</li>
))}
</ul>
</div>
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-5">
<h3 className="font-bold text-[#04102b] text-sm mb-3"> </h3>
<div className="space-y-2.5">
<a href="mailto:bgg8988@gmail.com" className="flex items-center gap-2.5 text-sm text-slate-600 hover:text-[#1a56db] transition group">
<div className="w-8 h-8 rounded-lg bg-[#f0f5ff] border border-[#dbe8ff] flex items-center justify-center group-hover:bg-[#1a56db] group-hover:border-[#1a56db] transition">
<svg className="w-4 h-4 text-[#1a56db] group-hover:text-white transition" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
bgg8988@gmail.com
</a>
<a href="tel:010-3907-1392" className="flex items-center gap-2.5 text-sm text-slate-600 hover:text-[#1a56db] transition group">
<div className="w-8 h-8 rounded-lg bg-[#f0f5ff] border border-[#dbe8ff] flex items-center justify-center group-hover:bg-[#1a56db] group-hover:border-[#1a56db] transition">
<svg className="w-4 h-4 text-[#1a56db] group-hover:text-white transition" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</div>
010-3907-1392
</a>
</div>
</div>
<div
className="rounded-2xl border border-[#1a3a7a] p-5 text-center"
style={{ background: '#04102b' }}
>
<div className="text-2xl font-extrabold text-white mb-0.5">24h</div>
<div className="text-[#5ba4ff] text-xs font-bold mb-1"> </div>
<div className="text-blue-300/40 text-xs"> · </div>
</div>
</div>
{/* 오른쪽: 폼 */}
<div className="md:col-span-3 bg-white rounded-2xl border border-[#dbe8ff] p-6">
<ContactForm />
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,220 +0,0 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { lunarToSolar } from '@/lib/lunar-utils';
export default function SajuForm() {
const router = useRouter();
const [year, setYear] = useState('');
const [month, setMonth] = useState('');
const [day, setDay] = useState('');
const [hour, setHour] = useState('');
const [calendarType, setCalendarType] = useState<'solar' | 'lunar'>('solar');
const [gender, setGender] = useState<'male' | 'female'>('male');
const [isLeapMonth, setIsLeapMonth] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!year || !month || !day) {
alert('생년월일을 모두 입력해주세요.');
return;
}
let finalYear = year;
let finalMonth = month;
let finalDay = day;
// 음력인 경우 양력으로 변환
if (calendarType === 'lunar') {
const solar = lunarToSolar(
parseInt(year),
parseInt(month),
parseInt(day),
isLeapMonth
);
finalYear = solar.year.toString();
finalMonth = solar.month.toString();
finalDay = solar.day.toString();
}
// URL 파라미터로 전달
const params = new URLSearchParams({
year: finalYear,
month: finalMonth,
day: finalDay,
gender,
calendarType,
originalYear: year,
originalMonth: month,
originalDay: day,
});
if (hour) {
params.append('hour', hour);
}
if (calendarType === 'lunar') {
params.append('isLeapMonth', isLeapMonth.toString());
}
router.push(`/saju/result?${params.toString()}`);
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
{/* 생년월일 */}
<div>
<label className="block text-left text-sm font-bold text-[#04102b] mb-3">
</label>
<div className="grid grid-cols-3 gap-3">
<input
type="number"
placeholder="년 (예: 1990)"
className="px-4 py-3 border-2 border-[#dbe8ff] rounded-xl focus:border-[#1a56db] focus:outline-none transition bg-white text-[#04102b]"
min="1900"
max="2100"
value={year}
onChange={(e) => setYear(e.target.value)}
required
/>
<input
type="number"
placeholder="월 (1-12)"
className="px-4 py-3 border-2 border-[#dbe8ff] rounded-xl focus:border-[#1a56db] focus:outline-none transition bg-white text-[#04102b]"
min="1"
max="12"
value={month}
onChange={(e) => setMonth(e.target.value)}
required
/>
<input
type="number"
placeholder="일 (1-31)"
className="px-4 py-3 border-2 border-[#dbe8ff] rounded-xl focus:border-[#1a56db] focus:outline-none transition bg-white text-[#04102b]"
min="1"
max="31"
value={day}
onChange={(e) => setDay(e.target.value)}
required
/>
</div>
</div>
{/* 태어난 시간 */}
<div>
<label className="block text-left text-sm font-bold text-[#04102b] mb-3">
()
</label>
<select
className="w-full px-4 py-3 border-2 border-[#dbe8ff] rounded-xl focus:border-[#1a56db] focus:outline-none transition bg-white text-[#04102b]"
value={hour}
onChange={(e) => setHour(e.target.value)}
>
<option value=""> / </option>
<option value="0"> () 23:00 - 01:00</option>
<option value="1"> () 01:00 - 03:00</option>
<option value="3"> () 03:00 - 05:00</option>
<option value="5"> () 05:00 - 07:00</option>
<option value="7"> () 07:00 - 09:00</option>
<option value="9"> () 09:00 - 11:00</option>
<option value="11"> () 11:00 - 13:00</option>
<option value="13"> () 13:00 - 15:00</option>
<option value="15"> () 15:00 - 17:00</option>
<option value="17"> () 17:00 - 19:00</option>
<option value="19"> () 19:00 - 21:00</option>
<option value="21"> () 21:00 - 23:00</option>
</select>
</div>
{/* 양력/음력 선택 */}
<div>
<label className="block text-left text-sm font-bold text-[#04102b] mb-3">
</label>
<div className="grid grid-cols-2 gap-3">
<button
type="button"
onClick={() => setCalendarType('solar')}
className={`px-6 py-3 rounded-xl font-bold transition ${
calendarType === 'solar'
? 'bg-[#1a56db] text-white shadow-lg'
: 'bg-white border-2 border-[#dbe8ff] text-[#04102b] hover:border-[#1a56db]'
}`}
>
</button>
<button
type="button"
onClick={() => setCalendarType('lunar')}
className={`px-6 py-3 rounded-xl font-bold transition ${
calendarType === 'lunar'
? 'bg-[#1a56db] text-white shadow-lg'
: 'bg-white border-2 border-[#dbe8ff] text-[#04102b] hover:border-[#1a56db]'
}`}
>
</button>
</div>
{calendarType === 'lunar' && (
<div className="mt-3">
<label className="flex items-center justify-center gap-2 text-sm text-slate-500 cursor-pointer">
<input
type="checkbox"
checked={isLeapMonth}
onChange={(e) => setIsLeapMonth(e.target.checked)}
className="w-4 h-4 text-[#1a56db] border-gray-300 rounded focus:ring-[#1a56db]"
/>
<span></span>
</label>
</div>
)}
</div>
{/* 성별 선택 */}
<div>
<label className="block text-left text-sm font-bold text-[#04102b] mb-3">
</label>
<div className="grid grid-cols-2 gap-3">
<button
type="button"
onClick={() => setGender('male')}
className={`px-6 py-3 rounded-xl font-bold transition ${
gender === 'male'
? 'bg-[#1a56db] text-white shadow-lg'
: 'bg-white border-2 border-[#dbe8ff] text-[#04102b] hover:border-[#1a56db]'
}`}
>
</button>
<button
type="button"
onClick={() => setGender('female')}
className={`px-6 py-3 rounded-xl font-bold transition ${
gender === 'female'
? 'bg-[#1a56db] text-white shadow-lg'
: 'bg-white border-2 border-[#dbe8ff] text-[#04102b] hover:border-[#1a56db]'
}`}
>
</button>
</div>
</div>
{/* 제출 버튼 */}
<button
type="submit"
className="w-full bg-gradient-to-r from-[#1a56db] to-[#7c3aed] hover:from-[#1e4fc2] hover:to-[#6d28d9] text-white py-4 rounded-xl text-lg font-bold transition shadow-lg hover:shadow-xl hover:scale-[1.02]"
>
</button>
<p className="text-sm text-slate-500 text-center">
* .
</p>
</form>
);
}

View File

@@ -1,41 +0,0 @@
import SajuForm from '../components/SajuForm';
export default function SajuInputPage() {
return (
<div className="min-h-full bg-[#f0f5ff]">
{/* Hero */}
<div className="relative overflow-hidden px-6 py-12"
style={{ background: '#04102b', backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.012) 0px, rgba(255,255,255,0.012) 1px, transparent 1px, transparent 40px)' }}>
<div className="relative max-w-xl mx-auto text-center">
<div className="inline-flex items-center gap-2 bg-violet-400/10 border border-violet-400/25 text-violet-300 text-xs font-semibold px-4 py-1.5 rounded-full mb-4 tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
AI ·
</div>
<h1 className="text-3xl md:text-4xl font-extrabold text-white leading-tight mb-3 tracking-tight">
</h1>
<p className="text-blue-200/60 text-sm leading-relaxed">
<br />
.
</p>
</div>
</div>
{/* Form 영역 */}
<div className="px-6 py-10 max-w-2xl mx-auto">
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-8 shadow-lg">
<div className="flex items-center gap-2 mb-6">
<div className="w-1 h-5 bg-gradient-to-b from-[#1a56db] to-[#7c3aed] rounded-full" />
<h2 className="font-bold text-[#04102b] text-base"> </h2>
</div>
<SajuForm />
</div>
<p className="text-center text-xs text-slate-400 mt-6">
.
</p>
</div>
</div>
);
}

View File

@@ -1,27 +0,0 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'AI 사주 분석',
description:
'생년월일시를 입력하면 Gemini AI가 사주팔자를 분석합니다. 일간·오행·대운·세운 기반 12개 항목 상세 해석. 재물운·애정운·직업·건강 포함.',
keywords: [
'AI 사주',
'사주풀이',
'사주팔자',
'사주 분석',
'오행 분석',
'대운',
'세운',
'사주 운세',
],
openGraph: {
title: 'AI 사주 분석 | 쟁승메이드',
description:
'Gemini AI 기반 사주팔자 분석. 일간·오행·대운·세운·재물운·애정운 12개 항목 해석.',
url: 'https://jaengseung-made.com/saju',
},
};
export default function SajuLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,334 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import PaymentButton from '@/app/components/PaymentButton';
import { createClient } from '@/lib/supabase/client';
const faqItems = [
{
q: '사주팔자란 무엇인가요?',
a: '사주팔자(四柱八字)는 태어난 년·월·일·시의 네 기둥(四柱)에 각각 천간과 지지 두 글자씩 총 여덟 글자(八字)로 이루어진 동양의 전통 운명 분석 체계입니다.',
},
{
q: 'AI 해석은 어떻게 동작하나요?',
a: '전통 명리학 계산 로직(오행, 신강/신약, 용신/희신 등)으로 산출된 데이터를 Gemini AI에 전달하여 12개 항목의 상세 해석을 생성합니다. 현재 기본 원국 분석과 AI 상세 해석 모두 무료로 제공됩니다.',
},
{
q: '태어난 시간을 모르면 어떻게 하나요?',
a: '시간을 모르더라도 년·월·일 세 기둥(三柱)만으로 사주를 계산할 수 있습니다. 다만 시주가 빠지면 세부 분석 정확도가 다소 낮아집니다.',
},
{
q: '음력으로 입력할 수 있나요?',
a: '네, 양력과 음력 모두 지원합니다. 음력을 선택하면 내부적으로 양력으로 변환하여 정확한 사주를 계산합니다. 윤달도 별도 선택이 가능합니다.',
},
];
interface SajuRecord {
id: number;
created_at: string;
saju_data: {
birth_year: number;
birth_month: number;
birth_day: number;
birth_hour?: number;
gender: string;
};
interpretation: string | null;
is_paid: boolean;
}
function buildResultUrl(rec: SajuRecord) {
const { birth_year, birth_month, birth_day, birth_hour, gender } = rec.saju_data;
if (!birth_year || !birth_month || !birth_day) return '/saju/input';
let url = `/saju/result?year=${birth_year}&month=${birth_month}&day=${birth_day}&gender=${gender}&calendarType=solar`;
if (birth_hour != null) url += `&hour=${birth_hour}`;
return url;
}
export default function SajuPage() {
const supabase = createClient();
const [paidRecords, setPaidRecords] = useState<SajuRecord[]>([]);
const [hasPaid, setHasPaid] = useState(false);
const [authChecked, setAuthChecked] = useState(false);
useEffect(() => {
async function fetchRecords() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) { setAuthChecked(true); return; }
const { data: records } = await supabase
.from('saju_records')
.select('*')
.eq('user_id', user.id)
.eq('is_paid', true)
.order('created_at', { ascending: false })
.limit(2);
if (records && records.length > 0) {
setPaidRecords(records);
setHasPaid(true);
}
setAuthChecked(true);
}
fetchRecords();
}, []);
return (
<div className="min-h-full bg-[#f0f5ff]">
{/* ─── Hero ─── */}
<div className="relative overflow-hidden bg-[#04102b] px-6 py-14 lg:px-12" style={{ backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 40px)' }}>
<div className="relative max-w-3xl mx-auto">
<div className="flex items-center gap-2 mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
<span className="text-violet-300/70 text-xs font-mono tracking-widest uppercase"> × AI · </span>
</div>
<h1 className="text-4xl md:text-5xl font-extrabold text-white leading-tight mb-5 tracking-tight">
AI가 <br />
<span className="text-amber-400"></span>
</h1>
<p className="text-blue-200/70 text-base md:text-lg leading-relaxed mb-8 max-w-xl mx-auto">
AI .<br />
12 .
</p>
{/* 이전 기록 있으면 분기 버튼, 없으면 단일 CTA */}
{authChecked && hasPaid ? (
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<Link
href="/saju/input"
className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-7 py-3.5 rounded-xl font-semibold text-base transition-all"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</Link>
<a
href="#past-records"
className="inline-flex items-center gap-2 bg-white/10 border border-white/20 text-white px-7 py-3.5 rounded-xl font-semibold text-base transition-all hover:bg-white/20"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</a>
</div>
) : (
<Link
href="/saju/input"
className="inline-flex items-center gap-2 bg-gradient-to-r from-[#1a56db] to-[#7c3aed] hover:from-[#1e4fc2] hover:to-[#6d28d9] text-white px-8 py-3.5 rounded-xl font-semibold text-base transition-all shadow-lg shadow-violet-900/40"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
</Link>
)}
</div>
</div>
<div className="px-6 py-12 lg:px-12">
<div className="max-w-4xl mx-auto space-y-10">
{/* ─── 이전 기록 섹션 (구매한 유저만) ─── */}
{hasPaid && paidRecords.length > 0 && (
<div id="past-records">
<div className="text-center mb-6">
<p className="text-violet-600 text-xs font-bold uppercase tracking-widest mb-2">MY RECORDS</p>
<h2 className="text-2xl font-extrabold text-[#04102b]"> AI </h2>
<p className="text-slate-500 text-sm mt-1"> </p>
</div>
<div className="grid md:grid-cols-2 gap-4">
{paidRecords.map((rec) => (
<div key={rec.id} className="bg-white rounded-2xl border border-[#dbe8ff] p-5 hover:border-violet-300 transition-colors">
<div className="flex items-start justify-between mb-3">
<div>
<div className="text-xs text-slate-400 mb-1">
{new Date(rec.created_at).toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' })}
</div>
<div className="font-bold text-[#04102b] text-base">
{rec.saju_data.birth_year ?? '?'}{' '}
{rec.saju_data.birth_month ?? '?'}{' '}
{rec.saju_data.birth_day ?? '?'}
</div>
<div className="text-sm text-slate-500 mt-0.5">
{rec.saju_data.gender === 'male' ? '남성' : '여성'}
{rec.saju_data.birth_hour != null ? ` · ${rec.saju_data.birth_hour}시생` : ''}
</div>
</div>
<span className="text-xs font-bold px-2 py-1 rounded-lg bg-amber-50 text-amber-600 border border-amber-200 flex-shrink-0">
AI
</span>
</div>
{rec.interpretation && (
<p className="text-xs text-slate-500 bg-slate-50 rounded-lg px-3 py-2 mb-3 line-clamp-2">
{rec.interpretation.replace(/[#*]/g, '').substring(0, 80)}...
</p>
)}
<Link
href={buildResultUrl(rec)}
className="block w-full text-center py-2 rounded-xl text-sm font-bold bg-[#04102b] hover:bg-[#0a1f5c] text-white border border-[#1a3a7a] transition"
>
</Link>
</div>
))}
</div>
</div>
)}
{/* ─── 바로 시작하기 CTA ─── */}
<div
className="rounded-2xl border border-[#1a3a7a] p-8 text-center"
style={{
background: '#04102b',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)',
}}
>
<h3 className="text-2xl font-extrabold text-white mb-2"> </h3>
<p className="text-blue-200/60 text-sm mb-6"> , </p>
<Link
href="/saju/input"
className="inline-flex items-center gap-2 bg-amber-400 hover:bg-amber-300 text-[#04102b] px-8 py-3.5 rounded-xl font-bold text-base transition-all"
>
</Link>
</div>
{/* ─── 무료 vs 유료 비교표 ─── */}
<div>
<div className="text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">PRICING</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] tracking-tight"> </h2>
<p className="text-slate-500 text-sm mt-2"> , AI 1,000</p>
</div>
<div className="grid md:grid-cols-2 gap-6">
{/* 무료 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6 shadow-sm">
<div className="flex items-center gap-3 mb-5">
<div className="w-10 h-10 rounded-xl bg-[#f0f5ff] border border-[#dbe8ff] flex items-center justify-center">
<svg className="w-5 h-5 text-[#1a56db]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<div className="text-xs font-bold text-slate-500 uppercase tracking-wide">FREE</div>
<div className="text-lg font-extrabold text-[#04102b]"> </div>
</div>
</div>
<ul className="space-y-3">
{[
'사주팔자 원국 (년·월·일·시주)',
'천간·지지·지장간 표',
'십성 및 십이운성',
'오행 분포 차트',
'지지 상호작용 (합·충·형)',
'일간 분석 요약',
].map((item) => (
<li key={item} className="flex items-center gap-2.5 text-sm text-slate-700">
<div className="w-4 h-4 rounded-full bg-blue-100 border border-blue-200 flex items-center justify-center flex-shrink-0">
<div className="w-1.5 h-1.5 rounded-full bg-[#1a56db]" />
</div>
{item}
</li>
))}
</ul>
<div className="mt-6 pt-5 border-t border-slate-100">
<div className="text-2xl font-extrabold text-[#04102b]"></div>
<div className="text-xs text-slate-500 mt-1"> </div>
<Link
href="/saju/input"
className="mt-4 block w-full text-center py-2.5 rounded-xl text-sm font-bold bg-[#f0f5ff] border border-[#dbe8ff] text-[#1a56db] hover:bg-blue-50 transition"
>
</Link>
</div>
</div>
{/* AI 해석 (현재 무료) */}
<div
className="rounded-2xl border border-[#1a3a7a] p-6 shadow-lg relative overflow-hidden"
style={{
background: '#04102b',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)',
}}
>
<div className="absolute top-4 right-4 bg-amber-400 text-[#04102b] text-xs font-bold px-2 py-0.5 rounded-lg">
1,000
</div>
<div className="flex items-center gap-3 mb-5 relative">
<div className="w-10 h-10 rounded-xl bg-violet-500/20 border border-violet-400/30 flex items-center justify-center">
<svg className="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<div>
<div className="text-xs font-bold text-violet-300 uppercase tracking-wide">AI PREMIUM</div>
<div className="text-lg font-extrabold text-white">AI </div>
</div>
</div>
<ul className="space-y-3 relative">
{[
'무료 기본 분석 전체 포함',
'신강/신약 정밀 판단',
'용신·희신·기신 추정',
'대운 (10년 주기) 분석',
'올해 세운 흐름',
'Gemini 2.5 Pro AI 12가지 상세 해석',
].map((item) => (
<li key={item} className="flex items-center gap-2.5 text-sm text-blue-200">
<div className="w-4 h-4 rounded-full bg-amber-400/20 border border-amber-400/40 flex items-center justify-center flex-shrink-0">
<div className="w-1.5 h-1.5 rounded-full bg-amber-400" />
</div>
{item}
</li>
))}
</ul>
<div className="mt-6 pt-5 border-t border-white/10 relative">
<div className="flex items-baseline gap-2 mb-1">
<span className="text-2xl font-extrabold text-white">1,000</span>
<span className="text-xs text-blue-300/50">/ 1</span>
</div>
<div className="text-xs text-blue-300/70 mt-1 mb-4"> · 12 AI </div>
<Link
href="/saju/input"
className="block w-full text-center py-3 rounded-xl text-sm font-bold transition bg-amber-400 text-[#04102b] hover:bg-amber-300"
>
</Link>
</div>
</div>
</div>
</div>
{/* ─── FAQ ─── */}
<div>
<div className="text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">FAQ</p>
<h2 className="text-2xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="space-y-4">
{faqItems.map((item, i) => (
<div key={i} className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<div className="flex items-start gap-3">
<div className="w-6 h-6 rounded-full bg-[#f0f5ff] border border-[#dbe8ff] flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-[#1a56db] text-xs font-bold">Q</span>
</div>
<div>
<p className="font-bold text-[#04102b] text-sm mb-2">{item.q}</p>
<p className="text-slate-600 text-sm leading-relaxed">{item.a}</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,443 +0,0 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import PaymentButton from '@/app/components/PaymentButton';
interface BirthKey {
birth_year: number;
birth_month: number;
birth_day: number;
birth_hour?: number;
gender: string;
}
interface SajuAISectionProps {
hasPaid: boolean;
savedInterpretation: string | null;
sajuData: object;
daeun: object | null;
daeunList: object[];
gender: string;
birthKey: BirthKey;
currentUrl: string;
engineData?: {
interactions?: any[];
shinsal?: any[];
gongmang?: any;
hiddenStems?: any[];
};
}
// ── 섹션별 메타 (아이콘·색상) ──────────────────────────────────────────
const SECTION_META: {
icon: string;
gradient: string;
border: string;
badge: string;
badgeText: string;
}[] = [
{ icon: '🌟', gradient: 'from-violet-500 to-purple-600', border: 'border-violet-100', badge: 'bg-violet-50 border-violet-200 text-violet-700', badgeText: '기질' },
{ icon: '⚖️', gradient: 'from-emerald-500 to-teal-600', border: 'border-emerald-100', badge: 'bg-emerald-50 border-emerald-200 text-emerald-700', badgeText: '오행' },
{ icon: '🔗', gradient: 'from-blue-500 to-indigo-600', border: 'border-blue-100', badge: 'bg-blue-50 border-blue-200 text-blue-700', badgeText: '지지' },
{ icon: '✨', gradient: 'from-amber-500 to-orange-500', border: 'border-amber-100', badge: 'bg-amber-50 border-amber-200 text-amber-700', badgeText: '신살' },
{ icon: '💰', gradient: 'from-yellow-500 to-amber-600', border: 'border-yellow-100', badge: 'bg-yellow-50 border-yellow-200 text-yellow-700', badgeText: '재물' },
{ icon: '🎯', gradient: 'from-rose-500 to-pink-600', border: 'border-rose-100', badge: 'bg-rose-50 border-rose-200 text-rose-700', badgeText: '직업' },
{ icon: '💕', gradient: 'from-pink-500 to-rose-500', border: 'border-pink-100', badge: 'bg-pink-50 border-pink-200 text-pink-700', badgeText: '애정' },
{ icon: '🌿', gradient: 'from-green-500 to-emerald-600', border: 'border-green-100', badge: 'bg-green-50 border-green-200 text-green-700', badgeText: '건강' },
{ icon: '🗺️', gradient: 'from-cyan-500 to-blue-600', border: 'border-cyan-100', badge: 'bg-cyan-50 border-cyan-200 text-cyan-700', badgeText: '대운' },
{ icon: '📅', gradient: 'from-indigo-500 to-violet-600', border: 'border-indigo-100', badge: 'bg-indigo-50 border-indigo-200 text-indigo-700', badgeText: '세운' },
{ icon: '🏆', gradient: 'from-amber-400 to-yellow-500', border: 'border-amber-100', badge: 'bg-amber-50 border-amber-200 text-amber-700', badgeText: '황금기' },
{ icon: '💌', gradient: 'from-slate-600 to-slate-800', border: 'border-slate-100', badge: 'bg-slate-50 border-slate-200 text-slate-700', badgeText: '종합' },
];
// ── 마크다운 → 섹션 파싱 ──────────────────────────────────────────────
interface ParsedSection {
number: number;
title: string;
content: string;
}
function parseInterpretation(text: string): ParsedSection[] {
// "## 숫자. 제목" 패턴으로 분리
const parts = text.split(/\n(?=##\s+\d+[\.\s])/).filter(Boolean);
const sections: ParsedSection[] = [];
for (const part of parts) {
const lines = part.trim().split('\n');
const headerLine = lines[0] ?? '';
const match = headerLine.match(/^##\s+(\d+)[.\s]\s*(.+)$/);
if (match) {
sections.push({
number: parseInt(match[1], 10),
title: match[2].trim(),
content: lines.slice(1).join('\n').trim(),
});
}
}
// 파싱 실패 시 전체를 하나의 섹션으로
if (sections.length === 0 && text.trim()) {
sections.push({ number: 0, title: 'AI 해석', content: text.trim() });
}
return sections;
}
// ── 섹션 카드 컴포넌트 ────────────────────────────────────────────────
function SectionCard({ section, meta, isOpen, onToggle }: {
section: ParsedSection;
meta: typeof SECTION_META[0];
isOpen: boolean;
onToggle: () => void;
}) {
return (
<div className={`rounded-2xl border-2 ${meta.border} bg-white overflow-hidden shadow-sm transition-all`}>
{/* 헤더 */}
<button
onClick={onToggle}
className="w-full flex items-center gap-3 p-4 text-left hover:bg-slate-50 transition-colors"
>
{/* 번호 아이콘 */}
<div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${meta.gradient} flex items-center justify-center text-white font-extrabold text-sm flex-shrink-0 shadow-sm`}>
{section.number > 0 ? section.number : meta.icon}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className={`text-[11px] font-bold px-2 py-0.5 rounded-full border ${meta.badge}`}>
{meta.badgeText}
</span>
<h3 className="font-extrabold text-[#04102b] text-sm leading-snug">
{section.title}
</h3>
</div>
</div>
{/* 토글 화살표 */}
<svg
className={`w-4 h-4 text-slate-400 flex-shrink-0 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* 내용 (아코디언) */}
{isOpen && (
<div className="px-5 pb-5 pt-1 border-t border-slate-100">
<div className={`text-[11px] font-semibold mb-3 flex items-center gap-1.5 ${meta.badge.includes('violet') ? 'text-violet-400' : 'text-slate-400'}`}>
<span className="text-base">{meta.icon}</span>
</div>
<div className="prose prose-sm max-w-none text-slate-700 leading-relaxed">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => <h1 className="text-base font-extrabold text-[#04102b] mt-4 mb-2">{children}</h1>,
h2: ({ children }) => <h2 className="text-sm font-extrabold text-[#04102b] mt-3 mb-1.5">{children}</h2>,
h3: ({ children }) => <h3 className="text-sm font-bold text-[#04102b] mt-2 mb-1">{children}</h3>,
p: ({ children }) => <p className="mb-3 text-sm leading-relaxed text-slate-700">{children}</p>,
strong: ({ children }) => <strong className="font-bold text-[#04102b]">{children}</strong>,
em: ({ children }) => <em className="italic text-slate-600">{children}</em>,
ul: ({ children }) => <ul className="list-disc list-inside space-y-1.5 mb-3 text-sm text-slate-700 pl-1">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal list-inside space-y-1.5 mb-3 text-sm text-slate-700 pl-1">{children}</ol>,
li: ({ children }) => <li className="leading-relaxed">{children}</li>,
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-violet-300 pl-4 py-1 my-3 text-slate-600 bg-violet-50 rounded-r-lg text-sm italic">
{children}
</blockquote>
),
hr: () => <hr className="border-slate-200 my-4" />,
code: ({ children }) => (
<code className="bg-slate-100 text-violet-700 px-1.5 py-0.5 rounded text-xs font-mono">
{children}
</code>
),
}}
>
{section.content}
</ReactMarkdown>
</div>
</div>
)}
</div>
);
}
// mock 데이터 여부 감지 (저장된 해석이 예시 데이터인 경우 재생성 필요)
function isMockInterpretation(text: string | null): boolean {
if (!text) return false;
return (
text.includes('API 키 문제 또는 할당량 초과') ||
text.includes('GEMINI_API_KEY 환경변수를 설정') ||
text.includes('예시 데이터를 보여드립니다') ||
text.includes('API 설정이 필요합니다')
);
}
// ── 메인 컴포넌트 ──────────────────────────────────────────────────────
export default function SajuAISection({
hasPaid,
savedInterpretation,
sajuData,
daeun,
daeunList,
gender,
birthKey,
currentUrl,
engineData,
}: SajuAISectionProps) {
// 저장된 해석이 mock 데이터면 재생성 필요
const isMock = isMockInterpretation(savedInterpretation);
const validSaved = savedInterpretation && !isMock ? savedInterpretation : null;
const [status, setStatus] = useState<'idle' | 'loading' | 'done' | 'error'>(
validSaved ? 'done' : 'idle'
);
const [interpretation, setInterpretation] = useState(validSaved ?? '');
const [openSections, setOpenSections] = useState<Set<number>>(new Set([0]));
const called = useRef(false);
const sections = parseInterpretation(interpretation);
const toggleSection = (idx: number) => {
setOpenSections(prev => {
const next = new Set(prev);
if (next.has(idx)) next.delete(idx);
else next.add(idx);
return next;
});
};
const expandAll = () => setOpenSections(new Set(sections.map((_, i) => i)));
const collapseAll = () => setOpenSections(new Set());
// 재생성: called ref 초기화 후 다시 API 호출
const handleRegenerate = () => {
called.current = false;
setStatus('idle');
setInterpretation('');
// idle → useEffect가 다시 실행되도록 상태 전환 트리거
setTimeout(() => {
called.current = false;
setStatus('loading');
fetch('/api/saju/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ saju: sajuData, daeun, daeunList, gender, engineData }),
})
.then(r => r.json())
.then(data => {
if (data.interpretation && !isMockInterpretation(data.interpretation)) {
setInterpretation(data.interpretation);
setStatus('done');
setOpenSections(new Set([0]));
// DB에 실제 해석으로 덮어쓰기
const { birth_year, birth_month, birth_day } = birthKey;
if (typeof birth_year === 'number' && typeof birth_month === 'number' && typeof birth_day === 'number') {
fetch('/api/saju/save-interpretation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ interpretation: data.interpretation, birthKey }),
}).catch(() => {});
}
} else {
setStatus('error');
}
})
.catch(() => setStatus('error'));
}, 0);
};
useEffect(() => {
if (!hasPaid || validSaved || called.current) return;
called.current = true;
setStatus('loading');
fetch('/api/saju/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ saju: sajuData, daeun, daeunList, gender, engineData }),
})
.then(r => r.json())
.then(data => {
if (data.interpretation) {
setInterpretation(data.interpretation);
setStatus('done');
// 첫 번째 섹션 자동 열기
setOpenSections(new Set([0]));
const { birth_year, birth_month, birth_day } = birthKey;
if (
typeof birth_year === 'number' && !isNaN(birth_year) &&
typeof birth_month === 'number' && !isNaN(birth_month) &&
typeof birth_day === 'number' && !isNaN(birth_day)
) {
fetch('/api/saju/save-interpretation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ interpretation: data.interpretation, birthKey }),
}).catch(() => {});
}
} else {
setStatus('error');
}
})
.catch(() => setStatus('error'));
}, [hasPaid]);
// ── 미결제 ──────────────────────────────────────────────────────────
if (!hasPaid) {
return (
<div className="bg-gradient-to-br from-[#04102b] via-[#0a1f5c] to-[#04102b] rounded-2xl border border-[#1a3a7a] p-7 text-center relative overflow-hidden">
<div className="absolute inset-0 opacity-[0.05]"
style={{ backgroundImage: 'radial-gradient(circle, #a78bfa 1px, transparent 1px)', backgroundSize: '22px 22px' }} />
<div className="relative">
<div className="inline-flex items-center gap-2 bg-amber-400/10 border border-amber-400/25 text-amber-300 text-xs font-semibold px-3 py-1 rounded-full mb-3">
AI PREMIUM
</div>
<h3 className="text-xl font-extrabold text-white mb-2">AI (12 )</h3>
<p className="text-blue-200/60 text-sm mb-6">
, , , , , <br />
Gemini 2.5 Pro가 .
</p>
{/* 미리보기 섹션 목록 */}
<div className="grid grid-cols-3 gap-2 mb-6 text-left">
{SECTION_META.map((meta, i) => (
<div key={i} className="flex items-center gap-1.5 bg-white/5 rounded-lg px-2 py-1.5">
<span className="text-sm">{meta.icon}</span>
<span className="text-xs text-blue-200/70 font-medium">{meta.badgeText}</span>
</div>
))}
</div>
<PaymentButton
productId="saju_detail"
className="inline-flex items-center gap-2 bg-amber-400 hover:bg-amber-300 text-[#04102b] font-bold px-7 py-3 rounded-xl transition-all"
>
AI 1,000
</PaymentButton>
<p className="text-blue-200/40 text-xs mt-3"> AI · </p>
</div>
</div>
);
}
// ── 로딩 ──────────────────────────────────────────────────────────
if (status === 'loading') {
return (
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-8 text-center">
<div className="w-10 h-10 border-2 border-violet-600 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-slate-500 text-sm font-medium">AI가 ...</p>
<p className="text-slate-400 text-xs mt-1"> 20~30 </p>
<div className="mt-5 flex flex-wrap justify-center gap-2">
{SECTION_META.map((meta, i) => (
<span key={i} className="flex items-center gap-1 text-xs text-slate-400 animate-pulse">
<span>{meta.icon}</span>{meta.badgeText}
</span>
))}
</div>
</div>
);
}
// ── 오류 ──────────────────────────────────────────────────────────
if (status === 'error') {
return (
<div className="bg-white rounded-2xl border border-red-200 p-6 text-center">
<p className="text-red-500 text-sm font-medium mb-3">AI .</p>
<button
onClick={() => { called.current = false; setStatus('idle'); }}
className="text-xs text-blue-600 underline"
>
</button>
</div>
);
}
// ── 해석 완료 ─────────────────────────────────────────────────────
return (
<div className="bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden">
{/* 헤더 */}
<div className="flex items-center gap-2 px-6 py-4 border-b border-slate-100 bg-gradient-to-r from-[#04102b] to-[#0a1f5c]">
<div className="w-7 h-7 rounded-lg bg-gradient-to-br from-violet-400 to-amber-400 flex items-center justify-center flex-shrink-0">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<div className="flex-1">
<h2 className="text-sm font-extrabold text-white">AI </h2>
<p className="text-blue-300/60 text-[11px]">12 · </p>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleRegenerate}
title="AI 해석 재생성"
className="text-[11px] text-blue-300/60 hover:text-blue-200 px-2 py-1 rounded-lg hover:bg-white/10 transition-all flex items-center gap-1"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
<span className="text-xs bg-emerald-400/20 border border-emerald-400/30 text-emerald-300 font-bold px-2.5 py-1 rounded-full">
</span>
</div>
</div>
{/* 섹션 컨트롤 + 목록 */}
<div className="p-5">
{/* 전체 펼치기/접기 */}
{sections.length > 1 && (
<div className="flex items-center justify-between mb-4">
<span className="text-xs text-slate-400 font-medium">
{sections.length}
</span>
<div className="flex gap-2">
<button
onClick={expandAll}
className="text-xs text-violet-600 hover:text-violet-800 font-semibold px-3 py-1 rounded-lg border border-violet-200 hover:bg-violet-50 transition-colors"
>
</button>
<button
onClick={collapseAll}
className="text-xs text-slate-500 hover:text-slate-700 font-semibold px-3 py-1 rounded-lg border border-slate-200 hover:bg-slate-50 transition-colors"
>
</button>
</div>
</div>
)}
{/* 섹션 카드 목록 */}
<div className="space-y-3">
{sections.map((section, idx) => {
const metaIdx = section.number > 0 ? Math.min(section.number - 1, SECTION_META.length - 1) : idx % SECTION_META.length;
const meta = SECTION_META[metaIdx];
return (
<SectionCard
key={idx}
section={section}
meta={meta}
isOpen={openSections.has(idx)}
onToggle={() => toggleSection(idx)}
/>
);
})}
</div>
{/* 하단 안내 */}
{sections.length > 0 && (
<p className="text-center text-xs text-slate-400 mt-5">
AI가 . .
</p>
)}
</div>
</div>
);
}

View File

@@ -1,323 +0,0 @@
'use client';
import { useMemo } from 'react';
// ── 천간 / 지지 ───────────────────────────────────────────────────────
const STEMS = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
const STEMS_KR = ['갑','을','병','정','무','기','경','신','임','계'];
const BRANCHES = ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
const BRANCHES_KR= ['자','축','인','묘','진','사','오','미','신','유','술','해'];
const STEM_ELEM: Record<string,string> = { '甲':'木','乙':'木','丙':'火','丁':'火','戊':'土','己':'土','庚':'金','辛':'金','壬':'水','癸':'水' };
const BRANCH_ELEM: Record<string,string> = { '子':'水','亥':'水','寅':'木','卯':'木','巳':'火','午':'火','申':'金','酉':'金','丑':'土','辰':'土','未':'土','戌':'土' };
// 1900-01-01 = 甲戌 (stem=0, branch=10) — CLAUDE.md 검증 완료
const BASE_MS = Date.UTC(1900, 0, 1);
function getTodayPillar() {
const now = new Date();
const todayMs = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
const diff = Math.round((todayMs - BASE_MS) / 86400000);
const si = ((0 + diff) % 10 + 10) % 10;
const bi = ((10 + diff) % 12 + 12) % 12;
return {
stem: STEMS[si], stemKr: STEMS_KR[si],
branch: BRANCHES[bi], branchKr: BRANCHES_KR[bi],
stemElem: STEM_ELEM[STEMS[si]] ?? '木',
branchElem: BRANCH_ELEM[BRANCHES[bi]] ?? '水',
year: now.getFullYear(), month: now.getMonth() + 1, date: now.getDate(),
};
}
// ── 오행 상생·상극 ────────────────────────────────────────────────────
const GENERATES: Record<string,string> = { '木':'火','火':'土','土':'金','金':'水','水':'木' };
const OVERCOMES: Record<string,string> = { '木':'土','火':'金','土':'水','金':'木','水':'火' };
type Rel = 'same'|'generates'|'generated'|'overcomes'|'overcome'|'neutral';
function getRelation(a: string, b: string): Rel {
if (a === b) return 'same';
if (GENERATES[a] === b) return 'generates';
if (GENERATES[b] === a) return 'generated';
if (OVERCOMES[a] === b) return 'overcomes';
if (OVERCOMES[b] === a) return 'overcome';
return 'neutral';
}
// ── 오늘 종합 점수 (0100) ────────────────────────────────────────────
function calcOverallScore(stemElem: string, branchElem: string, yongShin: string, heeShin: string) {
let score = 50;
const add = (rel: Rel, weight: number) => {
if (rel === 'same') score += 25 * weight;
else if (rel === 'generates' || rel === 'generated') score += 15 * weight;
else if (rel === 'overcomes') score -= 20 * weight;
else if (rel === 'overcome') score -= 8 * weight;
};
add(getRelation(stemElem, yongShin), 1);
add(getRelation(branchElem, yongShin), 0.8);
add(getRelation(stemElem, heeShin), 0.3);
add(getRelation(branchElem, heeShin), 0.2);
return Math.round(Math.max(10, Math.min(100, score)));
}
type Level = 'great'|'good'|'neutral'|'caution';
function toLevel(s: number): Level {
if (s >= 78) return 'great';
if (s >= 58) return 'good';
if (s >= 38) return 'neutral';
return 'caution';
}
// ── 결정론적 랜덤 ────────────────────────────────────────────────────
function seededRand(seed: number) {
let s = seed;
return () => { s = (s * 1664525 + 1013904223) & 0xffffffff; return (s >>> 0) / 0xffffffff; };
}
// ── 운세 항목 빌드 ────────────────────────────────────────────────────
type Area = { icon: string; label: string; score: number; desc: string };
const DESCS: Record<string, Record<Level, string>> = {
money: {
great: '재물 흐름이 활발합니다. 작은 투자나 구매 결정에 긍정적인 시기입니다.',
good: '재물 운이 순조롭습니다. 무리하지 않는 범위에서 움직이면 이익이 납니다.',
neutral: '수입·지출이 균형을 이루는 날. 큰 결정은 잠시 미루세요.',
caution: '충동 지출에 주의하세요. 중요한 금전 거래는 신중히 검토하세요.',
},
love: {
great: '감정 교류가 잘 이루어지는 날. 마음을 전하기 좋은 타이밍입니다.',
good: '관계에 따뜻한 기운이 감돕니다. 오래 연락 못 했던 사람에게 먼저 다가가 보세요.',
neutral: '평온한 관계를 유지하는 날입니다. 억지로 변화를 만들 필요 없습니다.',
caution: '오해가 생기기 쉬운 날입니다. 중요한 대화는 감정이 차분해진 후에 하세요.',
},
career: {
great: '능력이 잘 발휘되는 날. 중요한 프레젠테이션이나 면담에 최적입니다.',
good: '업무 효율이 올라가는 날입니다. 오늘 마무리한 과제는 좋은 결과로 이어집니다.',
neutral: '꾸준히 하던 일을 이어가는 날. 새 프로젝트보다 마무리에 집중하세요.',
caution: '실수가 생기기 쉬운 날입니다. 중요한 결재·계약은 하루 늦춰보세요.',
},
health: {
great: '체력·집중력 모두 좋은 날. 평소보다 활동량을 늘려도 괜찮습니다.',
good: '컨디션이 안정적입니다. 가벼운 운동으로 기운을 더 끌어올리세요.',
neutral: '무리하지 않는 것이 최선. 충분한 수분과 수면을 챙겨주세요.',
caution: '피로가 쌓이기 쉬운 날입니다. 무리한 약속은 피하고 충분히 쉬세요.',
},
social: {
great: '대인관계 운이 열린 날. 중요한 만남·협상에 유리한 시기입니다.',
good: '사교적 기운이 넘칩니다. 새 인맥을 만들거나 협업을 제안해보세요.',
neutral: '조용히 자신의 일에 집중하는 날. 복잡한 인간관계는 잠시 내려놓으세요.',
caution: '갈등이 생기기 쉬운 날입니다. 중요한 협상은 다음 기회로 미루는 것이 현명합니다.',
},
};
function buildAreas(
overall: number,
yongShin: string, heeShin: string,
yearNum: number, monthNum: number, dayNum: number,
): Area[] {
const now = new Date();
const seed = yearNum * 1_000_000 + monthNum * 10_000 + dayNum * 100 + now.getFullYear() % 100 * 10 + now.getMonth();
const rand = seededRand(seed);
const roll = () => Math.round(Math.max(15, Math.min(98, rand() * 40 + overall - 20)));
const keys = ['money','love','career','health','social'] as const;
const icons = ['💰','💕','🎯','🌿','🤝'];
const labels = ['재물운','애정운','직업운','건강운','사회운'];
return keys.map((k, i) => {
const s = roll();
return { icon: icons[i], label: labels[i], score: s, desc: DESCS[k][toLevel(s)] };
});
}
// ── 레벨별 색상/라벨 ─────────────────────────────────────────────────
const LEVEL_META: Record<Level, { emoji: string; label: string; bar: string; bg: string; border: string; text: string; badge: string }> = {
great: { emoji:'🌟', label:'아주 좋은 날', bar:'#f59e0b', bg:'bg-amber-50', border:'border-amber-300', text:'text-amber-800', badge:'bg-amber-100 text-amber-700 border-amber-300' },
good: { emoji:'✨', label:'좋은 날', bar:'#22c55e', bg:'bg-emerald-50',border:'border-emerald-300',text:'text-emerald-800',badge:'bg-emerald-100 text-emerald-700 border-emerald-300' },
neutral: { emoji:'🌤️', label:'평온한 날', bar:'#64748b', bg:'bg-slate-50', border:'border-slate-200', text:'text-slate-700', badge:'bg-slate-100 text-slate-600 border-slate-200' },
caution: { emoji:'⚠️', label:'조심하는 날', bar:'#f97316', bg:'bg-orange-50', border:'border-orange-300',text:'text-orange-800', badge:'bg-orange-100 text-orange-700 border-orange-300' },
};
const REL_DESC: (yongShin: string, yongShinKr: string) => Record<Rel, string> = (y, yk) => ({
same: `오늘 기운이 당신의 용신 ${y}(${yk})과 같은 오행으로 강하게 공명합니다.`,
generates: `오늘 기운이 용신 ${y}(${yk})을 생(生)해줍니다. 순조롭게 힘이 실리는 날.`,
generated: `용신 ${y}(${yk})이 오늘 기운을 생(生)해주고 있어 에너지를 베풀기 좋은 날입니다.`,
overcomes: `오늘 기운이 용신 ${y}(${yk})을 극(克)합니다. 신중하게 움직이는 것이 좋습니다.`,
overcome: `용신 ${y}(${yk})이 오늘 기운을 극(克)합니다. 주도적으로 판단하기 좋은 날.`,
neutral: `오늘 기운과 용신 ${y}(${yk})은 독립적으로 작용합니다. 차분하게 나아가세요.`,
});
// ── 점수 바 ──────────────────────────────────────────────────────────
function ScoreBar({ score, color }: { score: number; color: string }) {
return (
<div className="flex items-center gap-2 mt-1">
<div className="flex-1 h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div style={{ width: `${score}%`, background: color, transition: 'width 0.8s ease' }} className="h-full rounded-full" />
</div>
<span className="text-[10px] font-bold w-6 text-right" style={{ color }}>{score}</span>
</div>
);
}
// ── 메인 컴포넌트 ─────────────────────────────────────────────────────
interface Props {
yongShin: string;
yongShinKr: string;
heeShin: string;
heeShinKr: string;
yearNum: number;
monthNum: number;
dayNum: number;
hasLottoSubscription: boolean;
}
export default function SajuFortuneSection({
yongShin, yongShinKr, heeShin, heeShinKr,
yearNum, monthNum, dayNum,
hasLottoSubscription,
}: Props) {
const today = useMemo(getTodayPillar, []);
const overall = useMemo(() => calcOverallScore(today.stemElem, today.branchElem, yongShin, heeShin), [today, yongShin, heeShin]);
const level = toLevel(overall);
const meta = LEVEL_META[level];
const areas = useMemo(() => buildAreas(overall, yongShin, heeShin, yearNum, monthNum, dayNum), [overall, yongShin, heeShin, yearNum, monthNum, dayNum]);
const stemRel = getRelation(today.stemElem, yongShin);
const relDesc = REL_DESC(yongShin, yongShinKr)[stemRel];
return (
<>
{/* ── 상단 연결 화살표 ── */}
<div className="flex flex-col items-center gap-0 py-1">
<div className="w-px h-5 bg-gradient-to-b from-blue-200 to-amber-300" />
<div className="flex items-center gap-2 px-4 py-1.5 bg-amber-50 border border-amber-200 rounded-full text-[11px] font-bold text-amber-700">
<span></span>
</div>
<div className="w-px h-5 bg-gradient-to-b from-amber-300 to-amber-100" />
</div>
{/* ── 본문 카드 ── */}
<div id="today-fortune" className="bg-white rounded-2xl border border-amber-200 overflow-hidden shadow-sm">
{/* 헤더 */}
<div className="bg-gradient-to-r from-[#1a0a00] via-[#3d1a00] to-[#1a0a00] px-6 py-5">
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center flex-shrink-0 shadow-md text-xl">
</div>
<div>
<h2 className="text-sm font-extrabold text-white"> </h2>
<p className="text-amber-300/70 text-[11px] mt-0.5">
{today.year} {today.month} {today.date} · {today.stem}{today.branch} ({today.stemKr}{today.branchKr})
</p>
</div>
</div>
<span className={`text-[11px] font-extrabold px-3 py-1.5 rounded-full border ${meta.badge} flex-shrink-0`}>
{meta.emoji} {meta.label}
</span>
</div>
</div>
<div className="p-5 space-y-5">
{/* 일진 × 용신 분석 */}
<div className={`rounded-xl border p-4 ${meta.bg} ${meta.border}`}>
<div className="flex items-start gap-3">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center font-bold text-lg flex-shrink-0 ${meta.text}`}
style={{ background: 'rgba(255,255,255,0.65)' }}>
{today.stem}{today.branch}
</div>
<div className="flex-1">
<div className={`text-xs font-extrabold mb-1 ${meta.text}`}>
{yongShin}({yongShinKr})
</div>
<p className={`text-xs leading-relaxed ${meta.text}`} style={{ opacity: 0.88 }}>
{relDesc}
</p>
</div>
</div>
{/* 종합 점수 바 */}
<div className="mt-3 flex items-center gap-3">
<span className="text-[11px] font-bold text-slate-500"> </span>
<div className="flex-1 h-2.5 bg-white/70 rounded-full overflow-hidden border border-white/50">
<div
style={{ width: `${overall}%`, background: `linear-gradient(90deg, ${meta.bar}cc, ${meta.bar})` }}
className="h-full rounded-full"
/>
</div>
<span className="text-sm font-extrabold" style={{ color: meta.bar }}>{overall}</span>
</div>
</div>
{/* 5대 운세 그리드 */}
<div>
<h3 className="text-xs font-extrabold text-[#04102b] mb-3"> </h3>
<div className="space-y-3">
{areas.map((area) => {
const aLevel = toLevel(area.score);
const aMeta = LEVEL_META[aLevel];
return (
<div key={area.label} className="flex gap-3 items-start">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 text-sm ${aMeta.bg} border ${aMeta.border}`}>
{area.icon}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-0.5">
<span className="text-xs font-bold text-[#04102b]">{area.label}</span>
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded-full border ${aMeta.badge}`}>{aMeta.emoji}</span>
</div>
<ScoreBar score={area.score} color={aMeta.bar} />
<p className="text-[11px] text-slate-500 mt-1 leading-relaxed">{area.desc}</p>
</div>
</div>
);
})}
</div>
</div>
{/* 면책 */}
<p className="text-center text-[11px] text-slate-400 leading-relaxed">
({yongShinKr}·{yongShin}) .<br />
.
</p>
{/* 로또 CTA */}
<div className="rounded-2xl bg-gradient-to-br from-[#04102b] via-[#0d1f5c] to-[#04102b] border border-[#1a3a7a] p-5 relative overflow-hidden">
<div className="absolute inset-0 opacity-[0.04]"
style={{ backgroundImage: 'radial-gradient(circle, #a78bfa 1px, transparent 1px)', backgroundSize: '20px 20px' }} />
<div className="relative">
<div className="flex items-center gap-2 mb-2">
<span className="text-base">🎱</span>
<span className="text-xs font-extrabold text-amber-300">
{level === 'great' ? '오늘 운이 아주 좋습니다! 로또도 한 번 도전해보세요.' : '사주 기반 행운 번호도 확인해보세요.'}
</span>
</div>
<p className="text-xs text-blue-200/70 leading-relaxed mb-4">
<strong className="text-amber-300">{yongShin}({yongShinKr})</strong>
.
{hasLottoSubscription
? ' 구독 중이신 로또 서비스의 매주 최신 추천 번호도 함께 확인하세요.'
: ' 로또 구독 시 대운 교차 분석으로 더 정밀한 번호를 매주 받을 수 있어요.'}
</p>
<a
href="#saju-lotto-section"
onClick={e => {
e.preventDefault();
document.getElementById('saju-lotto-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}}
className="block w-full text-center bg-gradient-to-r from-amber-500 to-amber-400 hover:from-amber-400 hover:to-amber-300 text-[#04102b] text-sm font-extrabold px-4 py-2.5 rounded-xl transition-all shadow-lg cursor-pointer"
>
</a>
</div>
</div>
</div>
</div>
{/* 하단 연결 */}
<div className="flex flex-col items-center gap-0 py-1">
<div className="w-px h-5 bg-gradient-to-b from-amber-200 to-blue-300" />
<div className="flex items-center gap-2 px-4 py-1.5 bg-blue-50 border border-blue-200 rounded-full text-[11px] font-bold text-blue-700">
<span>🎱</span>
</div>
<div className="w-px h-5 bg-gradient-to-b from-blue-200 to-transparent" />
</div>
</>
);
}

View File

@@ -1,627 +0,0 @@
import { calculateSaju } from '@/lib/saju-calculator';
import Link from 'next/link';
import { calculateDaeun, getCurrentDaeun, getDaeunDescription } from '@/lib/daeun-calculator';
import { getCurrentSolarTerm, getSolarTermName, getSolarTermMonthBranch } from '@/lib/solar-terms';
import { EARTHLY_BRANCHES_KR, FIVE_ELEMENTS_KR, FIVE_ELEMENTS } from '@/lib/saju-calculator';
import { calculateElementScore, performFullAnalysis } from '@/lib/ai-interpretation';
import { createClient } from '@/lib/supabase/server';
import SajuAISection from './SajuAISection';
import SajuFortuneSection from './SajuFortuneSection';
interface PageProps {
searchParams: Promise<{
year: string;
month: string;
day: string;
hour?: string;
gender: 'male' | 'female';
calendarType: 'solar' | 'lunar';
originalYear?: string;
originalMonth?: string;
originalDay?: string;
isLeapMonth?: string;
}>;
}
export default async function SajuResultPage({ searchParams }: PageProps) {
const params = await searchParams;
const { year, month, day, hour, gender, calendarType, originalYear, originalMonth, originalDay, isLeapMonth } = params;
const yearNum = parseInt(year, 10);
const monthNum = parseInt(month, 10);
const dayNum = parseInt(day, 10);
const hourNum = hour ? parseInt(hour, 10) : null;
if (isNaN(yearNum) || isNaN(monthNum) || isNaN(dayNum)) {
return (
<div className="min-h-full bg-[#f0f5ff] flex items-center justify-center">
<div className="text-center py-20">
<p className="text-slate-500 text-sm mb-4"> . .</p>
<a href="/saju/input" className="text-blue-600 underline text-sm"> </a>
</div>
</div>
);
}
const inputYear = originalYear ? parseInt(originalYear) : yearNum;
const inputMonth = originalMonth ? parseInt(originalMonth) : monthNum;
const inputDay = originalDay ? parseInt(originalDay) : dayNum;
const isLunar = calendarType === 'lunar';
const isLeap = isLeapMonth === 'true';
// ── 사주팔자 계산 (TypeScript — lunar-javascript 기반 정밀 절기 계산) ──
const sajuData = calculateSaju(yearNum, monthNum, dayNum, hourNum, gender);
// 추가 분석 (신강신약, 용신, 오행균형, 세운)
const analysis = performFullAnalysis(sajuData);
const elementScores = analysis.elementScores;
// 대운
const currentYear = new Date().getFullYear();
const daeunList = calculateDaeun(
yearNum, monthNum, dayNum, gender,
sajuData.month.stem, sajuData.month.branch
);
const currentDaeun = getCurrentDaeun(daeunList, currentYear);
// 지지 상호작용 / 신살 / 공망 / 지장간
const branchInteractions = analysis.branchInteractions;
const shinsal = analysis.shinsal;
const gongmang = analysis.gongmang;
const hiddenStems = analysis.hiddenStems;
// ── 절기 정보 (표시용) ────────────────────────────────────────────────
const solarTermIndex = getCurrentSolarTerm(yearNum, monthNum, dayNum);
const solarTermName = getSolarTermName(solarTermIndex);
// ── 결제 여부 + 저장된 AI 해석 + 로또 구독 확인 ─────────────────────
let hasPaid = false;
let savedInterpretation: string | null = null;
let hasLottoSubscription = false;
try {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (user) {
// 사주 결제 확인 (anon client — 본인 orders는 RLS 허용 가정)
const { data: order } = await supabase
.from('orders').select('id')
.eq('user_id', user.id).eq('product_id', 'saju_detail').eq('status', 'paid')
.maybeSingle();
hasPaid = !!order;
if (hasPaid) {
// 1차: birth_hour 포함 정확한 키로 조회
const birthKey: Record<string, unknown> = { birth_year: yearNum, birth_month: monthNum, birth_day: dayNum, gender };
if (hourNum !== null) birthKey.birth_hour = hourNum;
const { data: record } = await supabase
.from('saju_records').select('interpretation')
.eq('user_id', user.id).eq('is_paid', true)
.contains('saju_data', birthKey).maybeSingle();
savedInterpretation = record?.interpretation ?? null;
// 2차 폴백: birth_hour 없이 조회 (시간 입력 안 한 케이스 or 불일치 방지)
if (!savedInterpretation) {
const birthKeyNoHour: Record<string, unknown> = { birth_year: yearNum, birth_month: monthNum, birth_day: dayNum, gender };
const { data: record2 } = await supabase
.from('saju_records').select('interpretation')
.eq('user_id', user.id).eq('is_paid', true)
.contains('saju_data', birthKeyNoHour).maybeSingle();
savedInterpretation = record2?.interpretation ?? null;
}
}
// 로또 구독 확인 — subscriptions 테이블 (세션 클라이언트로 RLS select_own 통과)
const { data: lottoSub } = await supabase
.from('subscriptions')
.select('id')
.eq('user_id', user.id)
.eq('status', 'active')
.in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
.maybeSingle();
hasLottoSubscription = !!lottoSub;
// subscriptions에서 못 찾으면 orders 테이블로 폴백 (구독 마이그레이션 전 데이터)
if (!hasLottoSubscription) {
const now = new Date().toISOString();
const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString();
const { data: lottoOrder } = await supabase
.from('orders')
.select('id, created_at')
.eq('user_id', user.id)
.eq('status', 'paid')
.in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
.gte('created_at', thirtyOneDaysAgo)
.maybeSingle();
hasLottoSubscription = !!lottoOrder;
}
}
} catch {
// 미로그인 시 무시
}
// ── 오행 색상 ──────────────────────────────────────────────────────────
const elementColors: { [k: string]: string } = {
'木': 'text-green-700', '火': 'text-red-600', '土': 'text-yellow-700',
'金': 'text-amber-600', '水': 'text-blue-700',
};
const elementBgColors: { [k: string]: string } = {
'木': 'bg-green-50 border-green-400', '火': 'bg-red-50 border-red-400',
'土': 'bg-yellow-50 border-yellow-400', '金': 'bg-amber-50 border-amber-400',
'水': 'bg-blue-50 border-blue-400',
};
// ── 띠 계산 ────────────────────────────────────────────────────────────
const zodiacAnimals = ['쥐', '소', '호랑이', '토끼', '용', '뱀', '말', '양', '원숭이', '닭', '개', '돼지'];
const zodiacIdx = (yearNum - 4) % 12;
const zodiacAnimal = zodiacAnimals[zodiacIdx >= 0 ? zodiacIdx : zodiacIdx + 12];
const engineBadge = <span className="text-[10px] bg-blue-50 border border-blue-200 text-blue-600 px-2 py-0.5 rounded-full font-semibold">TS </span>;
return (
<div className="min-h-full bg-[#f0f5ff]">
{/* 헤더 */}
<div className="bg-gradient-to-br from-[#04102b] via-[#0a1f5c] to-[#04102b] px-6 py-10">
<div className="max-w-4xl mx-auto text-center">
<div className="inline-flex items-center gap-2 bg-violet-400/10 border border-violet-400/25 text-violet-300 text-xs font-semibold px-4 py-1.5 rounded-full mb-4">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
</div>
<h1 className="text-3xl font-extrabold text-white mb-2"> </h1>
<p className="text-blue-200/60 text-sm"> AI </p>
</div>
</div>
<div className="px-6 py-8 max-w-4xl mx-auto">
<div className="grid lg:grid-cols-[280px_1fr] gap-6">
{/* 사이드바 */}
<aside className="lg:sticky lg:top-6 h-fit">
<div className="bg-[#04102b] rounded-2xl p-6 text-white">
<h2 className="text-base font-bold mb-5 text-center pb-4 border-b border-white/10"> </h2>
<div className="space-y-4 text-sm">
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="font-bold">
{isLunar ? (
<div>
<div> {inputYear}.{inputMonth}.{inputDay}{isLeap ? ' (윤달)' : ''}</div>
<div className="text-xs text-blue-300/50 mt-0.5"> {yearNum}.{monthNum}.{dayNum}</div>
</div>
) : (
<div>{yearNum}.{monthNum}.{dayNum}</div>
)}
</div>
</div>
{hourNum !== null && (
<div>
<div className="text-blue-300/60 mb-1"> </div>
<div className="font-bold">{hourNum}</div>
</div>
)}
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="font-bold">{gender === 'male' ? '남성' : '여성'}</div>
</div>
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="font-bold">{zodiacAnimal}</div>
</div>
<div>
<div className="text-blue-300/60 mb-1"> </div>
<div className="font-bold text-amber-300">{solarTermName} </div>
</div>
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="font-bold text-2xl text-amber-400">
{sajuData.day.stem} ({sajuData.day.stemKr})
</div>
<div className="text-xs text-blue-300/60 mt-1">
{FIVE_ELEMENTS_KR[sajuData.day.element as keyof typeof FIVE_ELEMENTS_KR]}({sajuData.day.element})
</div>
</div>
<div className="flex items-center gap-2">
<div className="text-blue-300/60 text-xs"> </div>
{engineBadge}
</div>
</div>
<div className="mt-5 pt-5 border-t border-white/10 space-y-2">
<Link href="/saju/input"
className="block w-full text-center bg-white/10 hover:bg-white/20 text-white px-4 py-2 rounded-lg transition text-sm font-medium">
</Link>
<Link href="/saju"
className="block w-full text-center bg-violet-500/20 hover:bg-violet-500/30 text-violet-300 px-4 py-2 rounded-lg transition text-sm font-medium">
</Link>
</div>
</div>
</aside>
{/* 메인 */}
<main className="space-y-6">
{/* 사주팔자 표 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h2 className="text-xl font-extrabold text-[#04102b] mb-5 text-center"> ()</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-[#04102b] text-white">
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
{sajuData.hour && <th className="py-2.5 px-3 text-center font-bold text-xs"></th>}
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
</tr>
</thead>
<tbody>
{/* 천간 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.hour.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.hour.stemKr}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xl font-bold text-[#04102b]">{sajuData.day.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.day.stemKr}</div>
<div className="text-xs text-amber-600 font-bold mt-0.5"></div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.month.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.month.stemKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.year.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.year.stemKr}</div>
</td>
</tr>
{/* 지지 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.hour.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.hour.branchKr}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xl font-bold text-[#04102b]">{sajuData.day.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.day.branchKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.month.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.month.branchKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.year.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.year.branchKr}</div>
</td>
</tr>
{/* 지장간 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs">
<div></div>
<div className="text-[10px] text-slate-400 font-normal"> </div>
</td>
{(() => {
const order = sajuData.hour
? ['시주', '일주', '월주', '년주']
: ['일주', '월주', '년주'];
return order.map((pillarName, idx) => {
const h = hiddenStems.find((hs: any) => hs.pillar === pillarName);
return (
<td key={idx} className={`py-2 px-2 text-center ${pillarName === '일주' ? 'bg-amber-50' : ''}`}>
{h && (
<div className="flex flex-wrap justify-center gap-1">
{h.stems.map((s: any, si: number) => (
<span key={si}
className={`inline-block px-1.5 py-0.5 rounded text-xs font-semibold border ${elementBgColors[s.element] || 'bg-gray-100'}`}
title={s.role}>
{s.stemKr}
</span>
))}
</div>
)}
</td>
);
});
})()}
</tr>
{/* 십성 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.hour.tenGod}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xs font-bold text-[#04102b]">{sajuData.day.tenGod}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.month.tenGod}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.year.tenGod}</div>
</td>
</tr>
{/* 십이운성 */}
<tr>
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.hour.fortune}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xs font-bold text-[#04102b]">{sajuData.day.fortune}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.month.fortune}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.year.fortune}</div>
</td>
</tr>
</tbody>
</table>
</div>
{/* 지지 상호작용 */}
{branchInteractions.length > 0 && (
<div className="mt-5 pt-5 border-t border-slate-100">
<h3 className="text-sm font-bold text-[#04102b] mb-3 text-center"> </h3>
<div className="flex flex-wrap justify-center gap-2">
{branchInteractions.map((inter: any, idx: number) => {
const isPositive = inter.type.includes('합');
const isNegative = inter.type.includes('충') || inter.type.includes('형');
const colorClass = isPositive
? 'bg-emerald-50 border-emerald-400 text-emerald-800'
: isNegative
? 'bg-red-50 border-red-400 text-red-800'
: 'bg-amber-50 border-amber-400 text-amber-800';
return (
<span key={idx} className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-bold border ${colorClass}`}
title={inter.description}>
{inter.type} {inter.branchesKr.join('')}
{inter.resultElement && `${FIVE_ELEMENTS_KR[inter.resultElement as keyof typeof FIVE_ELEMENTS_KR]}`}
</span>
);
})}
</div>
</div>
)}
{/* 오행 균형 */}
<div className="mt-5 pt-5 border-t border-slate-100">
<h3 className="text-sm font-bold text-[#04102b] mb-4 text-center"> </h3>
<div className="grid grid-cols-5 gap-2">
{Object.entries(elementScores).map(([element, score]) => (
<div key={element} className="text-center">
<div className={`text-lg font-bold mb-1 ${elementColors[element] || ''}`}>{element}</div>
<div className="text-xs text-slate-500 mb-2">
{FIVE_ELEMENTS_KR[element as keyof typeof FIVE_ELEMENTS_KR]}
</div>
<div className="w-full bg-slate-200 rounded-full h-1.5 mb-1">
<div
className={`h-1.5 rounded-full transition-all ${element === sajuData.day.element
? 'bg-gradient-to-r from-[#1a56db] to-[#7c3aed]'
: 'bg-slate-400'
}`}
style={{ width: `${Math.max(score, 5)}%` }}
/>
</div>
<div className="text-xs font-bold text-[#04102b]">{score}%</div>
</div>
))}
</div>
</div>
</div>
{/* 분석 카드 그리드 */}
<div className="grid md:grid-cols-2 gap-6">
{/* 신강/신약 + 용신 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h3 className="text-base font-extrabold text-[#04102b] mb-4"> </h3>
<div className="flex items-center gap-3 mb-4">
<span className={`inline-block px-4 py-1.5 rounded-xl text-sm font-bold ${
analysis.dayMasterStrength.result === '신강'
? 'bg-red-100 text-red-700 border-2 border-red-400'
: analysis.dayMasterStrength.result === '신약'
? 'bg-blue-100 text-blue-700 border-2 border-blue-400'
: 'bg-green-100 text-green-700 border-2 border-green-400'
}`}>
{analysis.dayMasterStrength.result}
</span>
<span className="text-slate-500 text-xs">: {analysis.dayMasterStrength.score}</span>
</div>
<ul className="space-y-1 text-xs text-slate-500 mb-5">
{analysis.dayMasterStrength.reasons.map((r: string, i: number) => (
<li key={i} className="flex items-start">
<span className="text-amber-500 mr-1.5">-</span>
<span>{r}</span>
</li>
))}
</ul>
<div className="border-t border-slate-100 pt-4">
<h4 className="font-bold text-[#04102b] mb-2.5 text-sm"> / / </h4>
<div className="flex flex-wrap gap-2 mb-3">
<span className={`px-2.5 py-1 rounded-lg text-xs font-bold border ${elementBgColors[analysis.yongShin.yongShin] || 'bg-gray-100'}`}>
: {analysis.yongShin.yongShinKr}
</span>
<span className={`px-2.5 py-1 rounded-lg text-xs font-bold border ${elementBgColors[analysis.yongShin.heeShin] || 'bg-gray-100'}`}>
: {analysis.yongShin.heeShinKr}
</span>
<span className="px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 border border-slate-300 text-slate-700">
: {analysis.yongShin.giShinKr}
</span>
</div>
<p className="text-xs text-slate-500 leading-relaxed">{analysis.yongShin.explanation}</p>
</div>
</div>
{/* 신살 + 공망 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h3 className="text-base font-extrabold text-[#04102b] mb-4"> ()</h3>
{shinsal.length > 0 ? (
<div className="space-y-2 mb-5">
{shinsal.map((s: any, i: number) => (
<div key={i} className="flex items-start gap-2 p-3 rounded-xl bg-[#f0f5ff]">
<span className="inline-block px-2 py-0.5 bg-[#04102b] text-white rounded-lg text-xs font-bold whitespace-nowrap">
{s.name}
</span>
<div>
<div className="text-xs font-semibold text-[#04102b]">
{s.pillar} {s.branchKr}
</div>
<div className="text-xs text-slate-500 mt-0.5">{s.description}</div>
</div>
</div>
))}
</div>
) : (
<p className="text-slate-500 text-xs mb-5"> .</p>
)}
<div className="border-t border-slate-100 pt-4">
<h4 className="font-bold text-[#04102b] mb-2 text-sm"> ()</h4>
<div className="flex gap-2 mb-2">
{gongmang.branchesKr.map((bk: string, i: number) => (
<span key={i} className="px-2.5 py-1 bg-[#04102b] text-white rounded-lg text-xs font-bold">
{bk}
</span>
))}
</div>
<p className="text-xs text-slate-500 leading-relaxed">{gongmang.description}</p>
</div>
{/* 세운 정보 */}
<div className="border-t border-slate-100 pt-4 mt-4">
<h4 className="font-bold text-[#04102b] mb-2 text-sm">
{analysis.seun.year}
</h4>
<div className="flex items-center gap-2 mb-2">
<span className={`px-2.5 py-1 rounded-lg text-xs font-bold border ${elementBgColors[analysis.seun.element] || 'bg-gray-100'}`}>
{analysis.seun.stemKr}{analysis.seun.branchKr}
</span>
<span className="text-xs text-slate-500">{analysis.seun.elementKr} </span>
</div>
{analysis.seun.interactions.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-2">
{analysis.seun.interactions.map((si: any, i: number) => (
<span key={i} className={`text-xs px-2 py-0.5 rounded-full font-semibold ${
si.type.includes('합') ? 'bg-emerald-50 text-emerald-700' : 'bg-red-50 text-red-700'
}`}>
{si.type} {si.branchesKr.join('')}
</span>
))}
</div>
)}
</div>
</div>
</div>
{/* AI 상세 해석 섹션 */}
{(() => {
const birthKey = {
birth_year: yearNum, birth_month: monthNum, birth_day: dayNum, gender,
...(hourNum !== null ? { birth_hour: hourNum } : {}),
};
const currentUrl = `/saju/result?year=${yearNum}&month=${monthNum}&day=${dayNum}${hourNum !== null ? `&hour=${hourNum}` : ''}&gender=${gender}&calendarType=${calendarType}${originalYear ? `&originalYear=${originalYear}&originalMonth=${originalMonth}&originalDay=${originalDay}` : ''}${isLeap ? '&isLeapMonth=true' : ''}`;
return (
<SajuAISection
hasPaid={hasPaid}
savedInterpretation={savedInterpretation}
sajuData={sajuData}
daeun={currentDaeun}
daeunList={daeunList}
gender={gender}
birthKey={birthKey}
currentUrl={currentUrl}
engineData={undefined}
/>
);
})()}
{/* 오늘의 운세 (사주 결제 시 표시) */}
{hasPaid && (
<SajuFortuneSection
yongShin={analysis.yongShin.yongShin}
yongShinKr={analysis.yongShin.yongShinKr}
heeShin={analysis.yongShin.heeShin}
heeShinKr={analysis.yongShin.heeShinKr}
yearNum={yearNum}
monthNum={monthNum}
dayNum={dayNum}
hasLottoSubscription={hasLottoSubscription}
/>
)}
{/* 대운 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h2 className="text-lg font-extrabold text-[#04102b] mb-5 text-center">
() 10
</h2>
{currentDaeun && (
<div className="bg-gradient-to-r from-[#04102b] to-[#0a2060] rounded-2xl p-5 mb-5 text-white">
<h3 className="text-sm font-bold mb-3 text-center text-blue-300"> </h3>
<div className="text-center mb-3">
<div className="text-3xl font-bold mb-1">
{currentDaeun.stem}{currentDaeun.branch}
</div>
<div className="text-base text-blue-200">
{currentDaeun.stemKr}{currentDaeun.branchKr}
</div>
<div className="text-xs text-blue-300/70 mt-1">
{currentDaeun.age} ~ {currentDaeun.age + 9} ({currentDaeun.startYear} ~ {currentDaeun.endYear})
</div>
</div>
<p className="text-center leading-relaxed text-xs text-blue-200/80">
{getDaeunDescription(currentDaeun, sajuData.day.stem)}
</p>
</div>
)}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{daeunList.map((daeun: any, index: number) => {
const isCurrent = currentDaeun &&
daeun.startYear === currentDaeun.startYear &&
daeun.endYear === currentDaeun.endYear;
return (
<div key={index}
className={`rounded-xl p-3 border-2 transition ${isCurrent ? 'bg-amber-50 border-amber-400' : 'bg-white border-[#dbe8ff]'}`}>
<div className="text-center">
<div className="text-xl font-bold text-[#04102b] mb-0.5">{daeun.stem}{daeun.branch}</div>
<div className="text-xs text-slate-500 mb-1.5">{daeun.stemKr}{daeun.branchKr}</div>
<div className="text-xs text-slate-400">{daeun.age} ~ {daeun.age + 9}</div>
<div className="text-xs text-slate-400">{daeun.startYear} ~ {daeun.endYear}</div>
{isCurrent && (
<div className="mt-1.5">
<span className="inline-block bg-[#04102b] text-white text-xs px-2.5 py-0.5 rounded-full font-semibold"></span>
</div>
)}
</div>
</div>
);
})}
</div>
</div>
</main>
</div>
</div>
</div>
);
}

View File

@@ -1,26 +0,0 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '블로그 자동화 솔루션 팩 | 쟁승메이드',
description:
'쿠팡파트너스·네이버 애드포스트·브랜드커넥트 수익을 자동화하는 프롬프트 조합법 45종 + 구조 템플릿 PDF 80p + 샘플 글 10편. ₩29,000 한 번 결제, 평생 무료 업데이트.',
keywords: [
'블로그 자동화',
'AI 블로그 글쓰기',
'쿠팡파트너스 자동화',
'애드포스트 수익화',
'네이버 블로그 SEO',
'ChatGPT 블로그',
'블로그 프롬프트',
],
openGraph: {
title: '블로그 자동화 솔루션 팩 | 쟁승메이드',
description:
'쿠팡파트너스·애드포스트 수익을 자동화하는 프롬프트 + 템플릿 + 샘플. ₩29,000.',
url: 'https://jaengseung-made.com/services/blog',
},
};
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,243 +0,0 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import PurchaseAgreementModal from '../../components/PurchaseAgreementModal';
const PACK_ITEMS = [
{
icon: '📝',
title: '프롬프트 조합법 45종',
desc: '상품리뷰 / 정보글 / 후기 / 비교 / 하우투 글별 최적 프롬프트 조합',
meta: 'PDF 80p',
},
{
icon: '📐',
title: '블로그 글 구조 템플릿 12종',
desc: '쿠팡파트너스 · 애드포스트 클릭을 유도하는 검증된 글 구조',
meta: 'Notion 템플릿',
},
{
icon: '💰',
title: '샘플 글 10편',
desc: '실제로 수익이 발생한 블로그 글 전문 + 해설 주석',
meta: '.docx · .md',
},
{
icon: '🔍',
title: '네이버 SEO 체크리스트',
desc: 'C-Rank · D.I.A. 알고리즘 대응 14가지 체크 포인트',
meta: 'PDF 20p',
},
];
const FAQS = [
{
q: '초보자도 쓸 수 있나요?',
a: 'ChatGPT나 Claude 계정만 있으면 됩니다. 프롬프트를 복붙하는 것부터 시작해서 점차 응용하도록 설계했습니다.',
},
{
q: '어떤 플랫폼에 맞나요?',
a: '네이버 블로그·티스토리·브런치 모두 대응. 쿠팡파트너스·애드포스트·브랜드커넥트 3가지 수익화 흐름을 모두 다룹니다.',
},
{
q: '업데이트는 얼마나 자주 되나요?',
a: '월 1~2회 주요 업데이트. 구매자 전용 Notion 페이지에서 변경 이력과 최신 파일을 제공합니다. 구매 후 12개월간 무료.',
},
{
q: '환불이 되나요?',
a: '전자상거래법상 디지털 콘텐츠는 제공 시작 후 환불이 제한됩니다. 구매 전 샘플 미리보기를 충분히 확인해주세요. 파일 손상·전달 불량은 즉시 재전달 또는 환불됩니다.',
},
];
export default function BlogServicePage() {
const [agreeOpen, setAgreeOpen] = useState(false);
const [openFaq, setOpenFaq] = useState<number | null>(0);
return (
<div className="min-h-full bg-white">
{/* HERO */}
<section className="relative overflow-hidden px-6 py-20 lg:px-14 lg:py-28 bg-gradient-to-br from-blue-50 via-white to-sky-50">
<div className="relative max-w-5xl mx-auto">
<p className="font-mono text-xs text-blue-700/70 tracking-[0.25em] uppercase mb-6">
Blog Automation Pack
</p>
<h1
className="text-[2.4rem] md:text-[3.2rem] lg:text-[4rem] font-extrabold leading-[1.08] tracking-tight text-slate-900 mb-6"
style={{ wordBreak: 'keep-all' }}
>
,
<br />
<span className="text-blue-700">AI에게 .</span>
</h1>
<p
className="text-slate-600 text-lg md:text-xl leading-relaxed mb-4 max-w-2xl"
style={{ wordBreak: 'keep-all' }}
>
· ·
<br />
<span className="text-slate-900 font-semibold"> · · </span>.
</p>
<div className="inline-flex items-center gap-3 bg-white border border-blue-200 rounded-xl px-5 py-3 mb-8 shadow-sm">
<span className="text-3xl font-extrabold text-blue-700 font-mono">29,000</span>
<span className="text-xs text-slate-500"> · 12 </span>
</div>
<div className="flex flex-wrap gap-3">
<button
onClick={() => setAgreeOpen(true)}
className="inline-flex items-center gap-2 bg-blue-700 hover:bg-blue-600 text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors shadow-lg shadow-blue-500/30"
>
</button>
<a
href="#sample"
className="inline-flex items-center gap-2 border border-slate-300 hover:border-blue-500 text-slate-700 hover:text-blue-700 px-8 py-4 rounded-xl font-semibold text-sm transition-all"
>
</a>
</div>
</div>
</section>
{/* PAIN POINTS */}
<section className="px-6 py-16 lg:px-14 bg-white">
<div className="max-w-5xl mx-auto">
<h2
className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-10 text-center"
style={{ wordBreak: 'keep-all' }}
>
.
</h2>
<div className="grid sm:grid-cols-3 gap-5">
{[
{ icon: '🕐', title: '매일 1시간+', desc: '글 소재·구성에 시간 다 쓰고 수익은 제자리' },
{ icon: '📉', title: '수익화 6개월+', desc: '블로그 키워놓고도 수익 구조가 안 잡힘' },
{ icon: '🤖', title: 'AI 글은 어색', desc: 'ChatGPT 그대로 복붙하면 바로 들통' },
].map((p) => (
<div key={p.title} className="border border-slate-200 rounded-2xl p-6 bg-slate-50/50">
<div className="text-3xl mb-3">{p.icon}</div>
<h3 className="font-extrabold text-slate-900 mb-2">{p.title}</h3>
<p className="text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{p.desc}
</p>
</div>
))}
</div>
</div>
</section>
{/* PACK CONTENT */}
<section id="sample" className="px-6 py-20 lg:px-14 bg-slate-50">
<div className="max-w-5xl mx-auto">
<p className="font-mono text-xs text-blue-700/70 tracking-widest uppercase mb-2">
Pack Contents
</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-10">
4
</h2>
<div className="grid md:grid-cols-2 gap-5">
{PACK_ITEMS.map((it) => (
<div
key={it.title}
className="flex gap-4 bg-white border border-slate-200 rounded-2xl p-6 hover:border-blue-400 transition-colors"
>
<div className="text-3xl flex-shrink-0">{it.icon}</div>
<div>
<div className="flex items-center gap-2 mb-1.5">
<h3 className="font-extrabold text-slate-900">{it.title}</h3>
<span className="text-[10px] font-mono text-blue-700 bg-blue-100 px-1.5 py-0.5 rounded">
{it.meta}
</span>
</div>
<p className="text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{it.desc}
</p>
</div>
</div>
))}
</div>
{/* Sample preview */}
<div className="mt-12 bg-white border-2 border-dashed border-blue-300 rounded-2xl p-8 relative overflow-hidden">
<span className="absolute top-4 right-4 text-[10px] font-bold text-blue-700 bg-blue-100 px-2 py-1 rounded">
</span>
<h4 className="font-extrabold text-slate-900 mb-3"> · </h4>
<pre className="text-xs font-mono text-slate-700 bg-slate-50 rounded-lg p-4 overflow-x-auto leading-relaxed">{`당신은 [카테고리] 전문 블로거입니다.
아래 상품의 [핵심 장점 3개]와 [주의점 1개]를 기반으로
C-Rank 알고리즘에 최적화된 1,200자 리뷰 글을 작성하세요.
[구조]
1. 후킹 도입 (공감형 질문)
2. 상품 요약 (스펙 표)
3. 실사용 관점 장점·단점
4. 대안 비교 (쿠팡 링크 삽입 지점: {LINK})
5. 결론 + 재질문 유도
[톤앤매너] 친근한 존댓말, 광고 느낌 최소화 ...`}</pre>
<p className="text-xs text-slate-500 mt-4">
45 .
</p>
</div>
</div>
</section>
{/* FAQ */}
<section className="px-6 py-20 lg:px-14 bg-white">
<div className="max-w-3xl mx-auto">
<h2 className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-8 text-center">
</h2>
<div className="space-y-3">
{FAQS.map((f, i) => (
<div key={i} className="border border-slate-200 rounded-xl overflow-hidden">
<button
onClick={() => setOpenFaq(openFaq === i ? null : i)}
className="w-full flex items-center justify-between px-5 py-4 text-left hover:bg-slate-50 transition-colors"
>
<span className="font-bold text-slate-900 text-sm">{f.q}</span>
<span className={`text-blue-700 text-xl transition-transform ${openFaq === i ? 'rotate-45' : ''}`}>
+
</span>
</button>
{openFaq === i && (
<div className="px-5 pb-5 text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{f.a}
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* FINAL CTA */}
<section className="px-6 py-20 lg:px-14 bg-gradient-to-br from-blue-700 to-blue-900">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl md:text-4xl font-extrabold text-white mb-4" style={{ wordBreak: 'keep-all' }}>
.
</h2>
<p className="text-blue-100 text-lg mb-8">29,000 · </p>
<button
onClick={() => setAgreeOpen(true)}
className="inline-flex items-center gap-2 bg-white text-blue-700 hover:bg-blue-50 px-10 py-4 rounded-xl font-extrabold text-base transition-colors shadow-xl"
>
</button>
<p className="text-blue-200/80 text-xs mt-6">
<Link href="/legal/refund" className="underline hover:text-white"> </Link>
{' · '}
<Link href="/legal/terms" className="underline hover:text-white"></Link>
</p>
</div>
</section>
<PurchaseAgreementModal
isOpen={agreeOpen}
onClose={() => setAgreeOpen(false)}
productName="블로그 자동화 솔루션 팩"
price="₩29,000"
/>
</div>
);
}

View File

@@ -1,28 +0,0 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'AI 음악 마스터 구조 팩 | Suno · MV · 유튜브 쇼츠',
description:
'엔지니어가 설계한 4단계 AI 음악 제작 공정. Suno 프롬프트 조합법 + MV 비디오 생성 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 입문 ₩39k / 프로 ₩99k / 마스터 ₩149k.',
keywords: [
'AI 음악 만들기',
'Suno 프롬프트',
'AI 뮤직비디오',
'AI 커버곡',
'유튜브 쇼츠 음악',
'AI 작곡',
'크리에이터 이코노미',
'Lyria 프롬프트',
'Runway AI 비디오',
],
openGraph: {
title: 'AI 음악 마스터 구조 팩 | 쟁승메이드',
description:
'네 사연을 노래로. 쇼츠까지 한 번에. 4단계 AI 음악 공정 · Suno Pro 검증 · 평생 업데이트.',
url: 'https://jaengseung-made.com/services/music',
},
};
export default function MusicLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,301 +0,0 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import PurchaseAgreementModal from '../../components/PurchaseAgreementModal';
import { SparklesOverlay } from '@/components/ui/sparkles-text';
import { CardBody, CardContainer, CardItem } from '@/components/ui/3d-card-effect';
type Tier = 'starter' | 'pro' | 'master';
const TIERS: Record<Tier, { name: string; price: string; priceNum: string; desc: string; features: string[]; highlight?: boolean }> = {
starter: {
name: '입문',
price: '₩39,000',
priceNum: '39,000',
desc: '첫 AI 음악을 위한 필수 구성',
features: [
'Suno 프롬프트 조합법 20종',
'구조 템플릿 PDF 40p',
'저작권 가이드 기본판',
'12개월 무료 업데이트',
],
},
pro: {
name: '프로',
price: '₩99,000',
priceNum: '99,000',
desc: '쇼츠 업로드까지 완성하는 풀세트',
highlight: true,
features: [
'입문 전체 포함',
'MV 워크플로우 (Runway · Luma · Pika)',
'샘플 프로젝트 1개 (.prj · 영상)',
'1:1 Q&A 1회 + 유튜브 SEO 템플릿',
],
},
master: {
name: '마스터',
price: '₩149,000',
priceNum: '149,000',
desc: '여러 장르·포맷을 커버하는 마스터피스',
features: [
'프로 전체 포함',
'샘플 프로젝트 장르별 3종',
'저작권 심화판 + 상업 이용 체크리스트',
'우선 업데이트 · 제작 레시피 영상',
],
},
};
const PROCESS = [
{ num: '01', subtitle: 'Concept & Lyrics', title: '크리에이티브 디렉팅', result: 'AI 최적화 가사 · 메타데이터 시트' },
{ num: '02', subtitle: 'Music Generation', title: '오디오 엔지니어링', result: '고품질 완곡 (Full Track, 스템 분리본)' },
{ num: '03', subtitle: 'AI MV Generation', title: '비주얼 마스터링', result: '쇼츠(9:16) 또는 유튜브(16:9) 고화질 영상' },
{ num: '04', subtitle: 'Viral Optimization', title: '퍼블리싱 가이드', result: '즉시 업로드 가능한 유튜브 배포 패키지' },
];
const FAQS = [
{
q: 'Suno 유료 플랜 가입이 꼭 필요한가요?',
a: 'Suno 무료 플랜은 상업적 이용이 제한됩니다. 본인 결과물을 유튜브·SNS에 업로드해 수익화하려면 Suno Pro 이상 권장. 팩 구매 후 가입 전 플랜 선택 가이드가 포함됩니다.',
},
{
q: '제가 만든 결과물의 상업 이용·저작권은?',
a: '결과물의 상업권은 고객이 가입한 AI 서비스의 이용약관을 따릅니다. 팩에는 Suno·Runway·Luma 각 서비스의 최신 약관 요약과 상업 이용 체크리스트가 포함되어 있습니다. (법률 자문이 아닌 참고용 가이드입니다.)',
},
{
q: '결과물 품질을 보장하나요?',
a: 'AI 생성물은 모델 버전·프롬프트 입력에 따라 달라지므로 결과물 자체를 보장하지 않습니다. 다만 팩은 동일 프롬프트로 반복 가능한 고품질 구간을 설계하는 방법을 제공합니다. 샘플 쇼츠·프로젝트로 품질 기대치를 사전 확인하세요.',
},
{
q: '환불이 가능한가요?',
a: '전자상거래법 제17조 제2항 제5호에 따라 디지털 콘텐츠는 제공 시작 후 청약철회가 제한됩니다. 무료 샘플로 사전 확인을 제공하므로 충분히 검토 후 구매해주세요. 파일 손상·전달 불량 등 회사 귀책은 즉시 재전달 또는 환불됩니다.',
},
{
q: '업데이트는 어떻게 받나요?',
a: '구매자 전용 Notion 페이지에서 변경 이력과 최신 파일을 제공. 12개월간 무료 업데이트가 기본, 마스터는 우선 업데이트·베타 선공개가 포함됩니다.',
},
];
export default function MusicServicePage() {
const [selectedTier, setSelectedTier] = useState<Tier | null>(null);
const [openFaq, setOpenFaq] = useState<number | null>(0);
return (
<div className="min-h-full bg-black text-white">
{/* PRICING */}
<section id="pricing" className="px-6 py-14 lg:px-14 bg-black">
<div className="max-w-6xl mx-auto">
<div className="flex items-end justify-between flex-wrap gap-3 mb-8">
<div>
<p className="font-mono text-xs text-white/50 tracking-widest uppercase mb-1">Pricing · 1 </p>
<h2 className="text-2xl md:text-3xl font-extrabold">3 , </h2>
</div>
<Link href="/services/music/samples" className="text-sm text-white/80 hover:text-white underline underline-offset-4">
</Link>
</div>
<div className="grid md:grid-cols-3 gap-5 items-stretch">
{(Object.keys(TIERS) as Tier[]).map((key) => {
const t = TIERS[key];
return (
<CardContainer key={key} containerClassName="w-full py-0" className="w-full h-full">
<CardBody
className={`relative w-full h-full rounded-2xl p-8 flex flex-col border transition-all ${
t.highlight
? 'border-white bg-white text-black md:scale-[1.03] md:-translate-y-2'
: 'border-white/15 bg-white/[0.02] hover:border-white/40 text-white'
}`}
>
{t.highlight && (
<SparklesOverlay
sparklesCount={20}
colors={{ first: '#9E7AFF', second: '#FE8BBB' }}
className="rounded-2xl"
/>
)}
{t.highlight && (
<CardItem translateZ={60} className="absolute -top-3 left-1/2 -translate-x-1/2 z-20">
<span className="inline-flex items-center bg-black text-white text-[10px] font-extrabold px-3 py-1.5 rounded-full uppercase tracking-wider border border-white">
</span>
</CardItem>
)}
<CardItem translateZ={40} as="h3" className="font-extrabold text-2xl mb-1 relative z-10">
{t.name}
</CardItem>
<CardItem translateZ={20} as="p" className={`text-sm mb-6 ${t.highlight ? 'text-black/60' : 'text-white/60'}`}>
{t.desc}
</CardItem>
<CardItem translateZ={50} className="mb-6">
<span className="text-4xl font-extrabold font-mono">{t.price}</span>
<span className={`text-xs ml-2 ${t.highlight ? 'text-black/50' : 'text-white/50'}`}>1 </span>
</CardItem>
<CardItem
translateZ={20}
as="ul"
className={`space-y-3 text-sm mb-8 flex-1 ${t.highlight ? 'text-black/80' : 'text-white/80'}`}
>
{t.features.map((f) => (
<li key={f} className="flex gap-2.5">
<span className="flex-shrink-0 mt-0.5">·</span>
<span className="leading-relaxed">{f}</span>
</li>
))}
</CardItem>
<CardItem
translateZ={40}
as="button"
onClick={() => setSelectedTier(key)}
className={`w-full py-4 rounded-xl font-extrabold text-sm transition-colors ${
t.highlight
? 'bg-black hover:bg-black/85 text-white'
: 'bg-white/10 hover:bg-white/20 text-white border border-white/20'
}`}
>
{t.name}
</CardItem>
</CardBody>
</CardContainer>
);
})}
</div>
<p className="text-xs text-white/50 text-center mt-8">
<Link href="/legal/refund" className="underline hover:text-white"> </Link> .
.
</p>
</div>
</section>
{/* 팩 구성품 */}
<section className="px-6 py-16 lg:px-14 bg-black border-t border-white/10">
<div className="max-w-6xl mx-auto">
<p className="font-mono text-xs text-white/50 tracking-widest uppercase mb-2">What&apos;s Included</p>
<h2 className="text-2xl md:text-3xl font-extrabold mb-8"> </h2>
<div className="grid md:grid-cols-2 gap-4">
{[
{ title: 'Suno 프롬프트 북', desc: '장르·무드·보컬 톤 조합법 20+종. 복붙해서 바로 사용하는 PDF.' },
{ title: 'MV 워크플로우', desc: 'Midjourney·Runway·Luma로 비트 싱크 영상 만드는 단계별 가이드.' },
{ title: '저작권 & 상업 이용', desc: 'Suno·Runway 약관 요약 + 수익화 전 안전 체크리스트.' },
{ title: '샘플 프로젝트 파일', desc: '완성된 가사·프롬프트·영상 세트. 그대로 수정해 재사용 가능.' },
].map((item) => (
<div
key={item.title}
className="p-6 rounded-2xl border border-white/15 bg-white/[0.02]"
>
<h3 className="font-bold text-white mb-1">{item.title}</h3>
<p className="text-sm text-white/60 leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* PROCESS */}
<section className="px-6 py-16 lg:px-14 bg-black border-t border-white/10">
<div className="max-w-6xl mx-auto">
<p className="font-mono text-xs text-white/50 tracking-widest uppercase mb-2">Process</p>
<h2 className="text-2xl md:text-3xl font-extrabold mb-10" style={{ wordBreak: 'keep-all' }}>
</h2>
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
{PROCESS.map((step) => (
<div
key={step.num}
className="rounded-2xl p-6 border border-white/15 bg-white/[0.02] hover:border-white/40 transition-colors"
>
<p className="font-mono text-xs text-white/50 mb-3">{step.num}</p>
<p className="font-mono text-[10px] text-white/50 uppercase tracking-widest mb-1">
{step.subtitle}
</p>
<h3 className="text-lg font-extrabold text-white mb-2">{step.title}</h3>
<p className="text-sm text-white/60 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{step.result}
</p>
</div>
))}
</div>
</div>
</section>
{/* SAMPLES */}
<section id="samples" className="px-6 py-12 lg:px-14 bg-black border-t border-white/10">
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<p className="font-mono text-xs text-white/50 tracking-widest uppercase mb-1">Samples</p>
<h2 className="text-xl md:text-2xl font-extrabold"> </h2>
</div>
<Link
href="/services/music/samples"
className="inline-flex items-center px-6 py-3 rounded-xl border border-white/30 hover:bg-white hover:text-black text-sm font-semibold text-white transition whitespace-nowrap"
>
</Link>
</div>
</section>
{/* FAQ */}
<section className="px-6 py-20 lg:px-14 bg-black border-t border-white/10">
<div className="max-w-3xl mx-auto">
<h2 className="text-2xl md:text-3xl font-extrabold text-center mb-10">
</h2>
<div className="space-y-3">
{FAQS.map((f, i) => (
<div key={i} className="border border-white/15 rounded-2xl overflow-hidden bg-white/[0.02]">
<button
onClick={() => setOpenFaq(openFaq === i ? null : i)}
className="w-full flex items-center justify-between px-5 py-4 text-left hover:bg-white/5 transition-colors"
>
<span className="font-bold text-white text-sm">{f.q}</span>
<span className={`text-white text-xl transition-transform ${openFaq === i ? 'rotate-45' : ''}`}>
+
</span>
</button>
{openFaq === i && (
<div className="px-5 pb-5 text-sm text-white/70 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{f.a}
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* Sticky CTA */}
<div
className="fixed bottom-0 inset-x-0 z-40 border-t border-white/15 backdrop-blur-md"
style={{ background: 'rgba(0,0,0,0.85)' }}
>
<div className="max-w-6xl mx-auto px-5 py-3 flex items-center justify-between gap-4">
<div className="min-w-0">
<p className="text-[11px] font-mono text-white/50 tracking-widest uppercase">From</p>
<p className="text-white font-extrabold text-lg leading-tight">
39,000 <span className="text-xs text-white/50 font-medium">· 1 </span>
</p>
</div>
<a
href="#pricing"
className="inline-flex items-center bg-white hover:bg-white/90 text-black px-6 py-3 rounded-xl font-extrabold text-sm transition-colors whitespace-nowrap"
>
</a>
</div>
</div>
<div className="h-20" aria-hidden />
{selectedTier && (
<PurchaseAgreementModal
isOpen={!!selectedTier}
onClose={() => setSelectedTier(null)}
productName={`AI 음악 마스터 팩 · ${TIERS[selectedTier].name}`}
price={TIERS[selectedTier].price}
/>
)}
</div>
);
}

View File

@@ -1,102 +0,0 @@
import type { Metadata } from 'next';
import Link from 'next/link';
export const metadata: Metadata = {
title: 'AI 음악·뮤비 샘플 갤러리',
description: '쟁승메이드 AI 음악 팩으로 제작한 샘플 뮤직비디오 모음. 장르별 결과물을 직접 확인하세요.',
};
type Sample = {
id: string;
title: string;
genre: string;
duration: string;
views?: string;
featured?: boolean;
embedId?: string;
};
const SAMPLES: Sample[] = [
{ id: 's1', title: 'K-POP 스타일 TOP 샘플', genre: 'K-POP', duration: '0:45', featured: true },
{ id: 's2', title: 'Lo-fi 감성 MV', genre: 'Lo-fi', duration: '1:02' },
{ id: 's3', title: '시티팝 무드 영상', genre: 'City Pop', duration: '0:58' },
{ id: 's4', title: 'EDM 쇼츠 훅', genre: 'EDM', duration: '0:30' },
{ id: 's5', title: '발라드 감성 컷', genre: 'Ballad', duration: '1:10' },
{ id: 's6', title: '트랩 비트 쇼츠', genre: 'Trap', duration: '0:35' },
];
export default function MusicSamplesPage() {
return (
<div className="px-6 py-20 lg:px-14" style={{ background: 'var(--kx-surface)' }}>
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<span className="kx-label">SAMPLE GALLERY</span>
<h1 className="kx-display text-3xl md:text-5xl font-bold mt-3 mb-4" style={{ color: 'var(--kx-on-surface)' }}>
AI ·
</h1>
<p className="max-w-2xl mx-auto text-sm md:text-base" style={{ color: 'var(--kx-on-variant)' }}>
. .
<br className="hidden md:block" />
<span className="text-xs" style={{ color: 'var(--kx-on-variant)' }}>
.
</span>
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 md:gap-6">
{SAMPLES.map((s) => (
<div
key={s.id}
className={`group relative aspect-[9/16] rounded-2xl overflow-hidden border ${
s.featured ? 'border-violet-400/50 shadow-2xl shadow-violet-900/40' : 'border-white/10'
}`}
style={{ background: 'linear-gradient(135deg, #1a0840 0%, #061228 100%)' }}
>
<div className="absolute inset-0 bg-gradient-to-br from-violet-500/15 to-cyan-500/10 group-hover:from-violet-500/25 group-hover:to-cyan-500/20 transition-all" />
{s.featured && (
<span
className="absolute top-3 left-3 z-10 text-[10px] px-2 py-1 rounded-full font-semibold tracking-widest"
style={{ background: 'rgba(204,151,255,0.2)', color: 'var(--kx-primary)', border: '1px solid rgba(204,151,255,0.5)' }}
>
TOP
</span>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center text-center p-5">
<div className="text-5xl mb-3 opacity-80 group-hover:scale-110 transition-transform">🎬</div>
<p className="text-[10px] md:text-xs font-mono tracking-widest text-violet-300/80 mb-1">{s.genre.toUpperCase()}</p>
<p className="text-sm md:text-base font-semibold" style={{ color: 'var(--kx-on-surface)' }}>
{s.title}
</p>
<p className="text-xs mt-1" style={{ color: 'var(--kx-on-variant)' }}>{s.duration}</p>
<p className="text-[10px] mt-3 opacity-60" style={{ color: 'var(--kx-on-variant)' }}>
{s.embedId ? '영상 재생' : '영상 준비 중'}
</p>
</div>
</div>
))}
</div>
<div
className="mt-16 text-center p-10 kx-glass"
style={{ border: '1px solid rgba(204,151,255,0.12)', borderRadius: '0.75rem 0.75rem 0.125rem 0.125rem' }}
>
<span className="kx-label">NEXT</span>
<h2 className="kx-display text-2xl md:text-3xl font-bold mt-2 mb-3" style={{ color: 'var(--kx-on-surface)' }}>
</h2>
<p className="text-sm mb-6" style={{ color: 'var(--kx-on-variant)' }}>
39,000.
</p>
<Link
href="/services/music#pricing"
className="kx-btn-primary px-8 py-3.5 rounded-full text-sm inline-flex"
>
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,31 +0,0 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '홈페이지·쇼핑몰·랜딩페이지 제작 | 반응형 웹 개발 외주',
description:
'소상공인·스타트업·기업 홈페이지, 쇼핑몰, 랜딩페이지 제작. 템플릿 없이 직접 개발. Next.js 기반 반응형 웹, SEO 기본 적용. 스타터 20만원~, 계약서 포함, 3개월 유지보수.',
keywords: [
'홈페이지 제작 외주',
'쇼핑몰 제작 외주',
'랜딩페이지 제작',
'소상공인 홈페이지',
'스타트업 웹사이트',
'반응형 홈페이지 제작',
'Next.js 개발 외주',
'기업 홈페이지 제작',
'웹사이트 제작 비용',
'SEO 최적화 홈페이지',
'홈페이지 개발 프리랜서',
],
openGraph: {
title: '홈페이지·쇼핑몰 제작 | 쟁승메이드',
description:
'소상공인·스타트업 홈페이지 제작. 템플릿 없이 직접 개발, SEO 포함, 20만원~.',
url: 'https://jaengseung-made.com/services/website',
},
robots: { index: false, follow: false },
};
export default function WebsiteLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,962 +0,0 @@
'use client';
import Link from 'next/link';
import { useState, useEffect, useRef } from 'react';
import ContactModal from '../../components/ContactModal';
import { trackCTAClick } from '../../../lib/gtag';
const samples = [
{
type: 'corporate',
title: '기업 홈페이지',
subtitle: '테크솔루션㈜',
desc: '"홈페이지가 없어서 B2B 영업 때마다 명함만 건넸다"는 고민을 해결한 기업 브랜드 사이트',
gradient: 'linear-gradient(135deg, #0a192f 0%, #112240 50%, #1a3a6c 100%)',
accent: '#4fc3f7',
tags: ['기업', 'B2B', '신뢰'],
icon: '🏢',
},
{
type: 'bakery',
title: '베이커리 홈페이지',
subtitle: '르 쁘띠 포르',
desc: '"인스타 팔로워는 많은데 실제 방문 예약이 없다"는 F&B 매장의 전환율 문제를 해결한 사이트',
gradient: 'linear-gradient(135deg, #78350f 0%, #92400e 50%, #d97706 100%)',
accent: '#fbbf24',
tags: ['F&B', '로컬', '예약'],
icon: '🥐',
},
{
type: 'portfolio',
title: '개인 포트폴리오',
subtitle: 'Kim Jisu',
desc: '"링크드인에 PDF 올리면 아무도 안 보더라"는 문제를 해결한 채용·수주 전환형 포트폴리오',
gradient: 'linear-gradient(135deg, #000000 0%, #0d0d0d 50%, #001a00 100%)',
accent: '#00ff88',
tags: ['크리에이터', '디자이너', '수주'],
icon: '✦',
},
{
type: 'dashboard',
title: '관리자 대시보드',
subtitle: 'DataFlow SaaS',
desc: '"엑셀로 수기 집계하다가 오류가 나서 야근"을 없애는 실시간 데이터 대시보드',
gradient: 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f2a3a 100%)',
accent: '#38bdf8',
tags: ['SaaS', '자동화', '관리'],
icon: '📊',
},
{
type: 'game',
title: '게임 매칭 시스템',
subtitle: 'NEXUS ARENA',
desc: '"디스코드에서 수동으로 팀 짜다가 싸움 났다"는 커뮤니티의 매칭·랭킹 자동화 플랫폼',
gradient: 'linear-gradient(135deg, #000000 0%, #0a0a1a 50%, #0d0029 100%)',
accent: '#a855f7',
tags: ['게임', '커뮤니티', '자동화'],
icon: '⚡',
},
{
type: 'interior',
title: '인테리어 업체 소개',
subtitle: 'AURUM Interior',
desc: '"포트폴리오 사진을 카톡으로만 보내다가 고급 고객을 놓쳤다"는 문제를 해결한 브랜드 사이트',
gradient: 'linear-gradient(135deg, #2C1810 0%, #4A3728 50%, #6B4E37 100%)',
accent: '#D4A853',
tags: ['인테리어', '포트폴리오', '고급'],
icon: '◈',
},
{
type: 'reading',
title: '독서 기록 노트',
subtitle: '나의 독서 기록',
desc: '읽은 책과 감상을 아름답게 기록하는 나만의 독서 저널 — 이런 것도 만들 수 있습니다',
gradient: 'linear-gradient(135deg, #0C0B09 0%, #1A1710 50%, #2A2218 100%)',
accent: '#D4A853',
tags: ['라이프', '독서', '기록'],
icon: '◻',
},
{
type: 'shopping',
title: '개인 쇼핑몰',
subtitle: 'MELLOW STUDIO',
desc: '"카페24 기본 테마가 너무 흔해서 브랜드가 안 살아난다"는 고민을 해결한 커스텀 쇼핑몰',
gradient: 'linear-gradient(135deg, #2A2018 0%, #4A3C2C 50%, #7A6A52 100%)',
accent: '#C4A882',
tags: ['쇼핑몰', '패션', '라이프'],
icon: '◇',
},
];
const processSteps = [
{ step: '01', title: '무료 상담', desc: '요구사항 파악 및 방향성 논의', icon: '💬' },
{ step: '02', title: '기획', desc: '사이트맵 & 와이어프레임', icon: '📋' },
{ step: '03', title: '디자인', desc: 'UI/UX 시안 제작', icon: '🎨' },
{ step: '04', title: '개발', desc: '반응형 퍼블리싱 & 기능 구현', icon: '⚙️' },
{ step: '05', title: '납품', desc: '검수 완료 후 도메인 배포', icon: '🚀' },
];
const plans = [
{
name: '스타터',
price: '20',
unit: '만원~',
color: '#38bdf8',
features: ['5페이지 이내', '반응형 디자인', '기본 SEO 설정', '1개월 유지보수', '3~5영업일 납품'],
note: '개인 블로그, 소규모 소개 사이트',
productId: 'website_starter',
},
{
name: '비즈니스',
price: '100',
unit: '만원~',
color: '#818cf8',
featured: true,
features: ['10페이지 이내', '반응형 디자인', '관리자 페이지', 'SEO 최적화', '3개월 유지보수', '1~2주 납품'],
note: '기업 사이트, 브랜드 페이지',
productId: 'website_business',
},
{
name: '프리미엄',
price: '200',
unit: '만원~',
color: '#f472b6',
features: ['페이지 수 무제한', '맞춤 디자인', '결제/회원 시스템', 'DB 연동', '6개월 유지보수', '일정 협의'],
note: '쇼핑몰, SaaS, 복합 시스템',
productId: 'website_premium',
},
];
const faqs = [
{
q: '제작 기간은 얼마나 걸리나요?',
a: '규모에 따라 다르지만, 스타터는 3~5영업일, 비즈니스는 1~2주, 프리미엄은 협의 후 결정합니다. 빠른 납품이 필요한 경우 별도 상담해 주세요.',
},
{
q: '수정은 몇 번까지 가능한가요?',
a: '기획 확정 후 디자인 시안 수정은 2회, 개발 완료 후 기능 수정은 유지보수 기간 내 자유롭게 가능합니다. 추가 기능 개발은 별도 견적으로 진행합니다.',
},
{
q: '도메인과 호스팅도 도와주시나요?',
a: '네, 도메인 구매부터 서버 세팅, 배포까지 전 과정을 도와드립니다. Vercel, AWS, 카페24 등 원하시는 플랫폼에 맞춰 배포해 드립니다.',
},
{
q: '앱(모바일 앱)이나 쇼핑몰도 개발 가능한가요?',
a: '네. iOS/Android 앱(React Native), 모바일 웹앱, 결제 연동 쇼핑몰, 회원/관리자 시스템 등 모두 개발 가능합니다. 프리미엄 플랜 이상이거나 규모에 따라 별도 견적으로 진행됩니다. 먼저 어떤 기능이 필요한지 상담해 주세요.',
},
{
q: '계약금은 어떻게 되나요? 중간에 취소하면 어떻게 되나요?',
a: '계약서 체결 후 착수금 30%를 먼저 입금받고 개발을 시작합니다. 납품 전 취소 시 완성 비율에 따라 정산하며, 착수 전 전액 환불됩니다. 모든 조건은 계약서에 명시됩니다.',
},
];
function useReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const scroller = (document.querySelector('.main-content') as HTMLElement | null) ?? document.documentElement;
const obs = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) { el.classList.add('ws-visible'); obs.disconnect(); } },
{ threshold: 0.1, root: scroller === document.documentElement ? null : scroller }
);
obs.observe(el);
return () => obs.disconnect();
}, []);
return ref;
}
function SampleMiniPreview({ type }: { type: string }) {
const W = 700, H = 350;
const inner = (content: React.ReactNode, bg: string) => (
<div style={{ height: 175, overflow: 'hidden', position: 'relative', background: bg, borderRadius: '20px 20px 0 0' }}>
<div style={{ width: W, height: H, transform: 'scale(0.5)', transformOrigin: 'top left', position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}>
{content}
</div>
</div>
);
switch (type) {
case 'corporate':
return inner(
<div style={{ background: '#fff', width: '100%', height: '100%', fontFamily: 'system-ui' }}>
<div style={{ height: 50, background: '#0a192f', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between' }}>
<div style={{ fontSize: 15, fontWeight: 900, color: '#4fc3f7', letterSpacing: '0.1em' }}>TECHSOLUTION</div>
<div style={{ display: 'flex', gap: 22 }}>
{['서비스','솔루션','고객사','연락처'].map(t => <span key={t} style={{ fontSize: 11, color: '#475569' }}>{t}</span>)}
</div>
<div style={{ fontSize: 11, background: '#1d4ed8', color: '#fff', padding: '7px 18px', borderRadius: 4, fontWeight: 700 }}> </div>
</div>
<div style={{ padding: '36px 32px', backgroundImage: 'linear-gradient(rgba(29,78,216,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(29,78,216,0.04) 1px, transparent 1px)', backgroundSize: '24px 24px' }}>
<div style={{ fontSize: 10, color: '#1d4ed8', letterSpacing: '0.18em', marginBottom: 12, fontWeight: 700 }}>ENTERPRISE IT SOLUTIONS</div>
<div style={{ fontSize: 36, fontWeight: 900, color: '#0a192f', lineHeight: 1.1, marginBottom: 14 }}> IT ,<br/> </div>
<div style={{ fontSize: 12, color: '#64748b', marginBottom: 22, lineHeight: 1.6 }}>15 IT .<br/>··DX .</div>
<div style={{ display: 'flex', gap: 10, marginBottom: 28 }}>
<div style={{ background: '#1d4ed8', color: '#fff', fontSize: 12, padding: '10px 22px', borderRadius: 6, fontWeight: 700 }}> </div>
<div style={{ border: '1px solid #cbd5e1', color: '#475569', fontSize: 12, padding: '10px 22px', borderRadius: 6 }}> </div>
</div>
<div style={{ display: 'flex', gap: 36 }}>
{[['15+','년 업력'],['340+','완료 프로젝트'],['180+','기업 고객'],['99.9%','가동률']].map(([n,l]) => (
<div key={l}><div style={{ fontSize: 22, fontWeight: 800, color: '#0a192f', letterSpacing: '-0.02em' }}>{n}</div><div style={{ fontSize: 9, color: '#94a3b8', marginTop: 3 }}>{l}</div></div>
))}
</div>
</div>
</div>, '#fff'
);
case 'bakery':
return inner(
<div style={{ background: '#fffbf5', width: '100%', height: '100%', fontFamily: 'Georgia, serif' }}>
<div style={{ height: 52, background: 'rgba(255,251,245,0.96)', borderBottom: '1px solid #fde8c8', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between' }}>
<div><div style={{ fontSize: 18, fontStyle: 'italic', color: '#78350f', fontWeight: 700 }}>Le Petit Fort</div><div style={{ fontSize: 8, color: '#b45309', letterSpacing: '0.2em' }}>ARTISAN BOULANGERIE</div></div>
<div style={{ display: 'flex', gap: 20 }}>
{['메뉴','스토리','예약'].map(t => <span key={t} style={{ fontSize: 11, color: '#92400e', fontFamily: 'system-ui' }}>{t}</span>)}
</div>
<div style={{ fontSize: 11, background: '#b45309', color: '#fff', padding: '7px 18px', borderRadius: 100, fontFamily: 'system-ui', fontWeight: 700 }}> </div>
</div>
<div style={{ padding: '28px 32px 0', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 28, alignItems: 'center' }}>
<div>
<div style={{ fontSize: 10, color: '#b45309', letterSpacing: '0.2em', marginBottom: 12, fontFamily: 'system-ui', fontWeight: 600 }}>Since 2018 · Paris Recipe</div>
<div style={{ fontSize: 38, fontWeight: 700, color: '#1c1008', lineHeight: 1.05, marginBottom: 14 }}> <br/><em></em><br/> </div>
<div style={{ fontSize: 11, color: '#92400e', marginBottom: 18, lineHeight: 1.7, fontFamily: 'system-ui' }}> <br/> .</div>
<div style={{ display: 'flex', gap: 10 }}>
<div style={{ background: '#b45309', color: '#fff', fontSize: 11, padding: '9px 20px', borderRadius: 100, fontFamily: 'system-ui', fontWeight: 700 }}> </div>
<div style={{ border: '1px solid #d97706', color: '#92400e', fontSize: 11, padding: '9px 20px', borderRadius: 100, fontFamily: 'system-ui' }}> </div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
{[{n:'버터 크루아상',p:'3,200',c:'#d97706'},{n:'소금빵',p:'2,800',c:'#b45309'},{n:'딸기 케이크',p:'7,500',c:'#be185d'},{n:'캄파뉴',p:'8,900',c:'#065f46'}].map(item => (
<div key={item.n} style={{ background: '#fff8f0', borderRadius: 10, padding: 10, border: '1px solid #fde8c8' }}>
<div style={{ height: 38, background: 'linear-gradient(135deg, #fde68a, #fbbf24)', borderRadius: 6, marginBottom: 6 }} />
<div style={{ fontSize: 9, color: '#1c1008', fontFamily: 'system-ui', fontWeight: 600 }}>{item.n}</div>
<div style={{ fontSize: 10, color: item.c, fontFamily: 'system-ui', fontWeight: 700 }}>{item.p}</div>
</div>
))}
</div>
</div>
</div>, '#fffbf5'
);
case 'portfolio':
return inner(
<div style={{ background: '#000', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', top: -40, left: '25%', width: 320, height: 320, background: 'radial-gradient(circle, rgba(0,255,136,0.06) 0%, transparent 70%)', pointerEvents: 'none' }} />
<div style={{ height: 50, background: 'rgba(0,0,0,0.95)', borderBottom: '1px solid rgba(0,255,136,0.1)', display: 'flex', alignItems: 'center', padding: '0 32px', justifyContent: 'space-between', position: 'relative', zIndex: 2 }}>
<div style={{ fontFamily: 'monospace', fontSize: 16, fontWeight: 700, color: '#00ff88' }}>KJ<span style={{ color: 'rgba(0,255,136,0.3)' }}>_</span></div>
<div style={{ display: 'flex', gap: 24 }}>
{['About','Work','Skills','Contact'].map(t => <span key={t} style={{ fontFamily: 'system-ui', fontSize: 10, color: '#374151', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{t}</span>)}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#00ff88' }} />
<span style={{ fontFamily: 'monospace', fontSize: 9, color: '#00ff88' }}>Available</span>
<div style={{ marginLeft: 8, border: '1px solid #00ff88', color: '#00ff88', fontSize: 9, padding: '5px 12px', borderRadius: 3, fontFamily: 'monospace', fontWeight: 700 }}>HIRE ME</div>
</div>
</div>
<div style={{ position: 'absolute', inset: 0, top: 50, backgroundImage: 'linear-gradient(rgba(0,255,136,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,136,0.04) 1px, transparent 1px)', backgroundSize: '32px 32px', pointerEvents: 'none' }} />
<div style={{ padding: '38px 32px', position: 'relative', zIndex: 2, display: 'grid', gridTemplateColumns: '1fr auto', gap: 32, alignItems: 'center' }}>
<div>
<div style={{ fontFamily: 'monospace', fontSize: 10, color: '#00ff88', letterSpacing: '0.15em', marginBottom: 16, border: '1px solid rgba(0,255,136,0.2)', display: 'inline-block', padding: '3px 10px' }}>FULL-STACK DEVELOPER</div>
<div style={{ fontSize: 56, fontWeight: 900, color: '#fff', lineHeight: 0.9, letterSpacing: '-0.03em', marginBottom: 18, fontFamily: 'system-ui' }}>Kim<br/><span style={{ color: '#00ff88' }}>Jisu</span></div>
<div style={{ fontSize: 11, color: '#4b5563', lineHeight: 1.7, marginBottom: 22, fontFamily: 'system-ui' }}>React · Next.js · TypeScript · Node.js<br/> .</div>
<div style={{ display: 'flex', gap: 10 }}>
<div style={{ background: '#00ff88', color: '#000', fontSize: 11, padding: '9px 22px', borderRadius: 4, fontWeight: 800, fontFamily: 'monospace' }}>VIEW WORK</div>
<div style={{ border: '1px solid rgba(0,255,136,0.3)', color: '#00ff88', fontSize: 11, padding: '9px 22px', borderRadius: 4, fontFamily: 'monospace' }}>CONTACT</div>
</div>
</div>
<div style={{ width: 130, height: 160, background: 'linear-gradient(135deg, #001a0d, #003322)', border: '1px solid rgba(0,255,136,0.2)', borderRadius: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<div style={{ width: 80, height: 80, borderRadius: '50%', border: '2px solid rgba(0,255,136,0.3)', background: 'rgba(0,255,136,0.05)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ fontSize: 26, color: '#00ff88', fontFamily: 'monospace', fontWeight: 700 }}>KJ</span>
</div>
</div>
</div>
</div>, '#000'
);
case 'dashboard':
return inner(
<div style={{ background: '#0f172a', width: '100%', height: '100%', display: 'flex', fontFamily: 'system-ui' }}>
<div style={{ width: 140, background: '#020617', borderRight: '1px solid rgba(255,255,255,0.05)', padding: '20px 14px' }}>
<div style={{ fontSize: 14, fontWeight: 800, color: '#38bdf8', marginBottom: 24, letterSpacing: '-0.02em' }}>DataFlow</div>
{['대시보드','분석','리포트','사용자','설정'].map((item, i) => (
<div key={item} style={{ fontSize: 10, color: i === 0 ? '#38bdf8' : '#475569', padding: '8px 10px', borderRadius: 6, marginBottom: 4, background: i === 0 ? 'rgba(56,189,248,0.1)' : 'transparent' }}>{item}</div>
))}
</div>
<div style={{ flex: 1, padding: '20px 22px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ fontSize: 14, fontWeight: 700, color: '#e2e8f0' }}> </div>
<div style={{ fontSize: 10, color: '#475569', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)', padding: '4px 12px', borderRadius: 6 }}> </div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10, marginBottom: 14 }}>
{[{l:'총 매출',v:'₩48.2M',c:'#38bdf8',u:true},{l:'신규 사용자',v:'1,247',c:'#34d399',u:true},{l:'전환율',v:'12.4%',c:'#a78bfa',u:false}].map(s => (
<div key={s.l} style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 8, padding: '12px 14px' }}>
<div style={{ fontSize: 8, color: '#475569', marginBottom: 6 }}>{s.l}</div>
<div style={{ fontSize: 20, fontWeight: 800, color: s.c, letterSpacing: '-0.02em' }}>{s.v}</div>
<div style={{ fontSize: 8, color: s.u ? '#34d399' : '#f87171', marginTop: 4 }}>{s.u ? '↑ +8.3%' : '↓ -1.2%'}</div>
</div>
))}
</div>
<div style={{ background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: 8, padding: 14, height: 110 }}>
<div style={{ fontSize: 9, color: '#475569', marginBottom: 10 }}> </div>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 6, height: 72 }}>
{[40,55,35,65,80,60,90,75,85,95,70,100].map((h, i) => (
<div key={i} style={{ flex: 1, height: `${h}%`, background: i === 11 ? '#38bdf8' : 'rgba(56,189,248,0.22)', borderRadius: '2px 2px 0 0' }} />
))}
</div>
</div>
</div>
</div>, '#0f172a'
);
case 'game':
return inner(
<div style={{ background: '#000', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', top: -60, left: '30%', width: 340, height: 340, background: 'radial-gradient(circle, rgba(168,85,247,0.14) 0%, transparent 70%)', pointerEvents: 'none' }} />
<div style={{ position: 'absolute', top: -20, right: '10%', width: 200, height: 200, background: 'radial-gradient(circle, rgba(6,182,212,0.1) 0%, transparent 70%)', pointerEvents: 'none' }} />
<div style={{ height: 50, background: 'rgba(0,0,0,0.9)', borderBottom: '1px solid rgba(6,182,212,0.2)', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between', position: 'relative', zIndex: 2 }}>
<div style={{ fontFamily: 'monospace', fontSize: 15, fontWeight: 900, color: '#06b6d4', letterSpacing: '0.15em' }}>NEXUS<span style={{ color: '#a855f7' }}>ARENA</span></div>
<div style={{ display: 'flex', gap: 20 }}>
{['랭킹','매칭','챔피언','스토어'].map(t => <span key={t} style={{ fontFamily: 'system-ui', fontSize: 10, color: '#374151', letterSpacing: '0.08em' }}>{t}</span>)}
</div>
<div style={{ background: 'linear-gradient(90deg, #06b6d4, #a855f7)', color: '#000', fontSize: 10, padding: '7px 18px', borderRadius: 3, fontWeight: 800, fontFamily: 'monospace' }}>PLAY NOW</div>
</div>
<div style={{ padding: '32px 32px', position: 'relative', zIndex: 2, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, alignItems: 'center' }}>
<div>
<div style={{ fontFamily: 'monospace', fontSize: 9, color: '#06b6d4', letterSpacing: '0.2em', marginBottom: 14 }}>SEASON 7 · COMPETITIVE</div>
<div style={{ fontSize: 50, fontWeight: 900, color: '#fff', lineHeight: 0.88, letterSpacing: '-0.03em', marginBottom: 18, fontFamily: 'system-ui' }}>NEXUS<br/><span style={{ background: 'linear-gradient(90deg, #06b6d4, #a855f7)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>ARENA</span></div>
<div style={{ fontSize: 10, color: '#4b5563', lineHeight: 1.65, marginBottom: 22, fontFamily: 'system-ui' }}> · · <br/> .</div>
<div style={{ display: 'flex', gap: 10 }}>
<div style={{ background: 'linear-gradient(90deg, #06b6d4, #a855f7)', color: '#fff', fontSize: 11, padding: '10px 22px', borderRadius: 4, fontWeight: 800, fontFamily: 'monospace' }}>PLAY NOW</div>
<div style={{ border: '1px solid rgba(6,182,212,0.4)', color: '#06b6d4', fontSize: 11, padding: '10px 22px', borderRadius: 4, fontFamily: 'monospace' }}> </div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
{[{name:'VIPER',role:'Assassin',c:'#06b6d4'},{name:'NOVA',role:'Mage',c:'#a855f7'},{name:'IRON',role:'Tank',c:'#94a3b8'},{name:'KIRA',role:'Support',c:'#ec4899'}].map(ch => (
<div key={ch.name} style={{ background: 'rgba(255,255,255,0.03)', border: `1px solid ${ch.c}30`, borderRadius: 8, padding: 10 }}>
<div style={{ height: 34, background: `linear-gradient(135deg, ${ch.c}20, ${ch.c}05)`, borderRadius: 4, marginBottom: 6, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ width: 24, height: 24, borderRadius: '50%', background: `${ch.c}30`, border: `1px solid ${ch.c}60` }} />
</div>
<div style={{ fontFamily: 'monospace', fontSize: 9, color: ch.c, fontWeight: 700 }}>{ch.name}</div>
<div style={{ fontSize: 8, color: '#374151', fontFamily: 'system-ui' }}>{ch.role}</div>
</div>
))}
</div>
</div>
</div>, '#000'
);
case 'interior':
return inner(
<div style={{ background: '#faf8f4', width: '100%', height: '100%' }}>
<div style={{ height: 50, background: '#2C1810', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between' }}>
<div><div style={{ fontFamily: 'Georgia, serif', fontSize: 14, color: '#D4A853', fontWeight: 700, letterSpacing: '0.12em' }}>AURUM</div><div style={{ fontSize: 7, color: '#6B4E37', letterSpacing: '0.25em' }}>INTERIOR DESIGN</div></div>
<div style={{ display: 'flex', gap: 18 }}>
{['포트폴리오','서비스','견적 문의'].map(t => <span key={t} style={{ fontSize: 9, color: '#9a8070', fontFamily: 'system-ui' }}>{t}</span>)}
</div>
<div style={{ border: '1px solid #D4A853', color: '#D4A853', fontSize: 9, padding: '6px 14px', fontFamily: 'system-ui', letterSpacing: '0.08em' }}>CONTACT</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: 300 }}>
<div style={{ padding: '32px 28px', display: 'flex', flexDirection: 'column', justifyContent: 'center', background: '#2C1810' }}>
<div style={{ fontSize: 9, color: '#D4A853', letterSpacing: '0.25em', marginBottom: 14, fontFamily: 'system-ui', textTransform: 'uppercase' }}>Premium Interior Design</div>
<div style={{ fontFamily: 'Georgia, serif', fontSize: 34, color: '#faf8f4', lineHeight: 1.1, marginBottom: 18 }}><br/><em></em><br/> </div>
<div style={{ fontSize: 10, color: '#9a8070', lineHeight: 1.7, fontFamily: 'system-ui', marginBottom: 22 }}>20 <br/> .</div>
<div style={{ display: 'inline-flex' }}><div style={{ background: '#D4A853', color: '#2C1810', fontSize: 10, padding: '10px 22px', fontFamily: 'system-ui', fontWeight: 700 }}> </div></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gridTemplateRows: '1fr 1fr', gap: 3 }}>
{['linear-gradient(135deg, #c9b99a, #a8927a)','linear-gradient(135deg, #8B7355, #6B5A47)','linear-gradient(135deg, #D4C5A9, #B8A88A)','linear-gradient(135deg, #7C6555, #5C4A3A)'].map((bg, i) => (
<div key={i} style={{ background: bg }}><div style={{ width: '100%', height: '100%', background: 'rgba(44,24,16,0.08)' }} /></div>
))}
</div>
</div>
</div>, '#faf8f4'
);
case 'reading':
return inner(
<div style={{ background: '#0C0B09', width: '100%', height: '100%' }}>
<div style={{ height: 46, background: '#0C0B09', borderBottom: '1px solid rgba(212,168,83,0.1)', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between' }}>
<div style={{ fontFamily: 'Georgia, serif', fontSize: 14, fontStyle: 'italic', color: '#D4A853', fontWeight: 600 }}> </div>
<div style={{ display: 'flex', gap: 18 }}>
{['서재','월별 기록','통계'].map(t => <span key={t} style={{ fontSize: 9, color: '#5c5040', fontFamily: 'system-ui' }}>{t}</span>)}
</div>
</div>
<div style={{ padding: '32px 32px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 28, alignItems: 'center' }}>
<div>
<div style={{ fontSize: 10, color: '#D4A853', letterSpacing: '0.2em', marginBottom: 14, fontFamily: 'system-ui', textTransform: 'uppercase' }}>My Reading Journal</div>
<div style={{ fontFamily: 'Georgia, serif', fontSize: 40, color: '#faf8f4', lineHeight: 1.05, marginBottom: 16 }}> <br/><em style={{ color: '#D4A853' }}></em><br/> </div>
<div style={{ fontSize: 10, color: '#5c5040', lineHeight: 1.7, fontFamily: 'system-ui', marginBottom: 22 }}> .<br/> .</div>
<div style={{ display: 'inline-flex', background: '#D4A853', color: '#0C0B09', fontSize: 10, padding: '9px 22px', fontFamily: 'system-ui', fontWeight: 700 }}> </div>
<div style={{ display: 'flex', gap: 24, marginTop: 22 }}>
{[['47','완독'],['1,240','페이지'],['12','이번 달']].map(([n,l]) => (
<div key={l}><div style={{ fontSize: 20, fontWeight: 800, color: '#D4A853', fontFamily: 'Georgia, serif' }}>{n}</div><div style={{ fontSize: 8, color: '#5c5040', fontFamily: 'system-ui', marginTop: 2 }}>{l}</div></div>
))}
</div>
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', justifyContent: 'center' }}>
{[{h:130,bg:'linear-gradient(180deg,#1e3a5f,#0a1628)',sp:'#2563eb'},{h:152,bg:'linear-gradient(180deg,#2C1810,#1a0e0a)',sp:'#D4A853'},{h:118,bg:'linear-gradient(180deg,#1a1a1a,#0d0d0d)',sp:'#6b7280'},{h:142,bg:'linear-gradient(180deg,#1e1b4b,#0f0d2e)',sp:'#7c3aed'},{h:120,bg:'linear-gradient(180deg,#064e3b,#022c22)',sp:'#10b981'}].map((b, i) => (
<div key={i} style={{ width: 38, height: b.h, background: b.bg, borderRadius: '3px 3px 0 0', borderLeft: `3px solid ${b.sp}40`, boxShadow: '2px 0 8px rgba(0,0,0,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ width: 1, height: '80%', background: `${b.sp}30` }} />
</div>
))}
</div>
</div>
</div>, '#0C0B09'
);
case 'shopping':
return inner(
<div style={{ background: '#F4F2EF', width: '100%', height: '100%' }}>
<div style={{ height: 52, background: '#F4F2EF', borderBottom: '1px solid #E0DDD8', display: 'flex', alignItems: 'center', padding: '0 28px', justifyContent: 'space-between' }}>
<div style={{ fontSize: 16, fontWeight: 900, color: '#0C0B09', letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'system-ui' }}>MELLOW<span style={{ fontWeight: 300 }}> STUDIO</span></div>
<div style={{ display: 'flex', gap: 20 }}>
{['NEW','OUTER','TOP','BOTTOM'].map(t => <span key={t} style={{ fontSize: 9, color: '#7C7870', fontFamily: 'system-ui', letterSpacing: '0.1em' }}>{t}</span>)}
</div>
<div style={{ display: 'flex', gap: 14, fontSize: 12, color: '#0C0B09', fontFamily: 'system-ui' }}><span>🔍</span><span>🛍 2</span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: 298 }}>
<div style={{ background: 'linear-gradient(135deg, #2A2018, #4A3C2C)', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', overflow: 'hidden' }}>
<div style={{ width: 120, height: 200, background: 'linear-gradient(180deg, #c8b89a, #9a8a72)', borderRadius: 4, boxShadow: '16px 16px 40px rgba(0,0,0,0.35)' }} />
<div style={{ position: 'absolute', bottom: 16, left: 16 }}>
<div style={{ fontSize: 9, color: 'rgba(244,242,239,0.5)', letterSpacing: '0.2em', fontFamily: 'system-ui' }}>NEW ARRIVAL</div>
<div style={{ fontSize: 17, fontWeight: 900, color: '#F4F2EF', fontFamily: 'system-ui', letterSpacing: '-0.01em' }}>SS 2025</div>
</div>
</div>
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<div>
<div style={{ fontSize: 9, color: '#7C7870', letterSpacing: '0.2em', marginBottom: 10, fontFamily: 'system-ui' }}>COLLECTION</div>
<div style={{ fontSize: 30, fontWeight: 900, color: '#0C0B09', lineHeight: 1.05, fontFamily: 'system-ui', letterSpacing: '-0.02em', marginBottom: 12 }}>Quiet<br/>Luxury</div>
<div style={{ fontSize: 10, color: '#7C7870', lineHeight: 1.65, fontFamily: 'system-ui', marginBottom: 18 }}> .<br/> .</div>
<div style={{ display: 'inline-flex', background: '#0C0B09', color: '#F4F2EF', fontSize: 9, padding: '9px 20px', letterSpacing: '0.15em', fontFamily: 'system-ui', fontWeight: 700 }}>SHOP NOW</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
{[['#c8b89a','₩328K'],['#8a7860','₩498K'],['#d4c5a9','₩218K']].map(([bg, p], i) => (
<div key={i} style={{ borderRadius: 3, overflow: 'hidden' }}>
<div style={{ height: 52, background: `linear-gradient(160deg, ${bg}, rgba(0,0,0,0.08))` }} />
<div style={{ fontSize: 8, color: '#7C7870', fontFamily: 'system-ui', paddingTop: 4 }}>{p}</div>
</div>
))}
</div>
</div>
</div>
</div>, '#F4F2EF'
);
default:
return <div style={{ height: 175, background: '#0a0f1e', borderRadius: '20px 20px 0 0' }} />;
}
}
export default function WebsiteServicePage() {
const [openFaq, setOpenFaq] = useState<number | null>(null);
const [showTop, setShowTop] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [modalService, setModalService] = useState('홈페이지 제작');
const openModal = (service: string) => {
trackCTAClick(service, '/services/website');
setModalService(service);
setModalOpen(true);
};
useEffect(() => {
const scroller = (document.querySelector('.main-content') as HTMLElement | null) ?? document.documentElement;
const onScroll = () => setShowTop(scroller.scrollTop > 400);
scroller.addEventListener('scroll', onScroll, { passive: true });
return () => scroller.removeEventListener('scroll', onScroll);
}, []);
const samplesRef = useReveal();
const processRef = useReveal();
const pricingRef = useReveal();
const faqRef = useReveal();
const ctaRef = useReveal();
return (
<div style={{ background: '#030712', minHeight: '100vh', color: 'white', fontFamily: "'Pretendard', 'Apple SD Gothic Neo', system-ui, sans-serif" }}>
<ContactModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
service={modalService}
checklist={[
'원하시는 홈페이지 종류 (소개/쇼핑몰/SaaS 등)',
'참고하고 싶은 사이트 URL (있으면)',
'필요한 주요 페이지 및 기능',
'희망 납품 일정 및 예산 범위',
'디자인 선호 스타일 (모던/감성/심플 등)',
]}
accentColor="text-indigo-400"
headerFrom="#0a0a1a"
headerTo="#1e1b4b"
/>
<style dangerouslySetInnerHTML={{ __html: `
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.min.css');
* { box-sizing: border-box; }
word-break { word-break: keep-all; }
/* scroll reveal */
.ws-reveal {
opacity: 0;
transform: translateY(32px);
filter: blur(3px);
transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1),
transform 0.7s cubic-bezier(0.16,1,0.3,1),
filter 0.7s cubic-bezier(0.16,1,0.3,1);
}
.ws-reveal.ws-visible { opacity: 1; transform: translateY(0); filter: blur(0); }
.ws-reveal > *:nth-child(1) { transition-delay: 0ms; }
.ws-reveal > *:nth-child(2) { transition-delay: 80ms; }
.ws-reveal > *:nth-child(3) { transition-delay: 160ms; }
.ws-reveal > *:nth-child(4) { transition-delay: 240ms; }
.ws-reveal > *:nth-child(5) { transition-delay: 320ms; }
@keyframes ws-fadeUp {
from { opacity: 0; transform: translateY(28px); filter: blur(4px); }
to { opacity: 1; transform: translateY(0); filter: blur(0); }
}
@keyframes ws-gridScroll {
from { background-position: 0 0; }
to { background-position: 48px 48px; }
}
@keyframes ws-glow {
0%,100% { opacity: 0.5; }
50% { opacity: 1; }
}
.ws-sample-card {
border-radius: 20px; overflow: hidden;
border: 1px solid rgba(255,255,255,0.07);
background: #0a0f1e; cursor: pointer;
transition: transform 0.45s cubic-bezier(0.16,1,0.3,1),
box-shadow 0.45s cubic-bezier(0.16,1,0.3,1),
border-color 0.3s;
}
.ws-sample-card:hover {
transform: translateY(-6px);
box-shadow: 0 24px 64px rgba(0,0,0,0.5);
border-color: rgba(255,255,255,0.14);
}
.ws-plan-card {
transition: transform 0.4s cubic-bezier(0.16,1,0.3,1), box-shadow 0.4s;
}
.ws-plan-card:hover { transform: translateY(-4px); }
.ws-faq-item {
border-radius: 14px; overflow: hidden;
transition: border-color 0.25s;
}
.ws-step-card {
transition: transform 0.4s cubic-bezier(0.16,1,0.3,1), box-shadow 0.4s;
}
.ws-step-card:hover {
transform: translateY(-4px);
box-shadow: 0 16px 48px rgba(0,0,0,0.3);
}
/* 모바일 반응형 */
@media (max-width: 640px) {
.ws-portfolio-grid { grid-template-columns: 1fr !important; }
.ws-process-steps { flex-direction: column !important; align-items: stretch !important; }
.ws-process-divider { display: none !important; }
.ws-pricing-grid { grid-template-columns: 1fr !important; }
.ws-hero-stats { gap: 0 !important; flex-wrap: nowrap !important; }
.ws-hero-stats > div { padding: 0 16px !important; }
.ws-cta-box { padding: 36px 24px !important; }
}
@media (max-width: 480px) {
.ws-hero-buttons { flex-direction: column !important; align-items: stretch !important; }
.ws-hero-buttons a, .ws-hero-buttons button { text-align: center !important; justify-content: center !important; }
}
/* scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: #030712; }
::-webkit-scrollbar-thumb { background: rgba(99,102,241,0.3); border-radius: 2px; }
`}} />
{/* ── Hero ── */}
<section style={{ padding: '80px 24px 60px', position: 'relative', overflow: 'hidden' }}>
{/* Diagonal pattern */}
<div style={{
position: 'absolute', inset: 0, pointerEvents: 'none',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 40px)',
}} />
<div style={{ maxWidth: 820, margin: '0 auto', position: 'relative', animation: 'ws-fadeUp 0.9s cubic-bezier(0.16,1,0.3,1) both' }}>
<p style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.18em',
color: '#6366f1', textTransform: 'uppercase',
fontFamily: 'monospace',
marginBottom: 24,
}}>
</p>
<h1 style={{
fontSize: 'clamp(28px, 4.5vw, 54px)', fontWeight: 800,
lineHeight: 1.2, marginBottom: 20,
letterSpacing: '-0.02em',
color: '#ffffff',
wordBreak: 'keep-all',
}}>
·· ,<br/>
</h1>
<p style={{
fontSize: 16, color: '#64748b', lineHeight: 1.85, marginBottom: 36,
wordBreak: 'keep-all',
}}>
SaaS·· <br/>
. 10 .
</p>
<div className="ws-hero-buttons" style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
<Link href="/freelance?service=website" style={{
display: 'inline-flex', alignItems: 'center', gap: 8,
padding: '14px 28px',
background: '#6366f1',
borderRadius: 12, color: 'white', fontWeight: 700, fontSize: 15,
textDecoration: 'none',
transition: 'background 0.2s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#4f46e5'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#6366f1'; }}>
</Link>
<a href="#samples" style={{
display: 'inline-flex', alignItems: 'center', gap: 8,
padding: '14px 28px',
border: '1px solid rgba(255,255,255,0.1)', borderRadius: 12,
color: '#94a3b8', fontWeight: 600, fontSize: 15,
textDecoration: 'none',
transition: 'border-color 0.3s, color 0.3s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.25)'; (e.currentTarget as HTMLElement).style.color = '#e2e8f0'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.1)'; (e.currentTarget as HTMLElement).style.color = '#94a3b8'; }}>
</a>
</div>
{/* Stats */}
<div className="ws-hero-stats" style={{ display: 'flex', gap: 0, justifyContent: 'center', marginTop: 56, flexWrap: 'wrap' }}>
{[
{ num: '3~5일', label: '최단 납품 (스타터)' },
{ num: '20만원~', label: '시작 가격' },
{ num: '전액환불', label: '납품 전 환불 보장' },
].map((s, i) => (
<div key={s.label} style={{
textAlign: 'center', padding: '0 40px',
borderRight: i < 2 ? '1px solid rgba(255,255,255,0.08)' : 'none',
}}>
<div style={{ fontSize: 22, fontWeight: 800, color: 'white', letterSpacing: '-0.02em' }}>{s.num}</div>
<div style={{ fontSize: 12, color: '#475569', marginTop: 4, letterSpacing: '0.02em' }}>{s.label}</div>
</div>
))}
</div>
</div>
</section>
{/* ── Feature tags ── */}
<div style={{ borderTop: '1px solid rgba(255,255,255,0.05)', borderBottom: '1px solid rgba(255,255,255,0.05)', padding: '14px 24px' }}>
<div style={{ maxWidth: 1000, margin: '0 auto', display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center' }}>
{['반응형 디자인', 'SEO 최적화', '웹앱·모바일웹', '계약서 작성', '소스코드 제공', '납기 패널티 보장', '도메인 배포'].map((t) => (
<span key={t} style={{ padding: '4px 12px', fontSize: '11px', color: '#475569', letterSpacing: '0.06em', border: '1px solid rgba(255,255,255,0.07)', borderRadius: 4, fontFamily: 'monospace' }}>
{t}
</span>
))}
</div>
</div>
{/* ── Trust badges ── */}
<section style={{ padding: '48px 24px', maxWidth: 1000, margin: '0 auto' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 1, border: '1px solid rgba(255,255,255,0.06)', borderRadius: 12, overflow: 'hidden' }}>
{[
{
title: '계약서 필수 작성', desc: '모든 프로젝트 계약서 체결 후 진행',
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 20, height: 20 }}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z" /></svg>,
},
{
title: '주간 진행 보고', desc: '매주 작업 현황 공유, 연락 두절 없음',
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 20, height: 20 }}><path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" /></svg>,
},
{
title: '소스코드 전액 제공', desc: '완성 후 전체 소스코드 인도',
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 20, height: 20 }}><path strokeLinecap="round" strokeLinejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" /></svg>,
},
{
title: '납기 지연 패널티', desc: '지연 1일당 10만원 자동 감면',
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 20, height: 20 }}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /></svg>,
},
{
title: '실시간 진행 현황', desc: '마이페이지에서 7단계 진행 상황 직접 확인',
icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 20, height: 20 }}><path strokeLinecap="round" strokeLinejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>,
},
].map((b) => (
<div key={b.title} style={{
padding: '20px 22px',
background: 'rgba(255,255,255,0.02)',
display: 'flex', gap: 14, alignItems: 'flex-start',
}}>
<span style={{ color: '#6366f1', flexShrink: 0, marginTop: 2 }}>{b.icon}</span>
<div>
<div style={{ fontSize: 13, fontWeight: 600, color: '#e2e8f0', marginBottom: 4, wordBreak: 'keep-all' }}>{b.title}</div>
<div style={{ fontSize: 12, color: '#475569', lineHeight: 1.6, wordBreak: 'keep-all' }}>{b.desc}</div>
</div>
</div>
))}
</div>
</section>
{/* ── Sample Portfolio ── */}
<section id="samples" style={{ padding: '56px 24px', maxWidth: 1160, margin: '0 auto' }}>
<div ref={samplesRef} className="ws-reveal">
<div style={{ textAlign: 'center', marginBottom: 44 }}>
<p style={{ fontSize: 11, color: '#6366f1', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12, fontWeight: 700 }}>Portfolio Samples</p>
<h2 style={{ fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10, letterSpacing: '-0.02em' }}>
</h2>
<p style={{ color: '#475569', fontSize: 14 }}>
</p>
</div>
<div className="ws-portfolio-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 18 }}>
{samples.map((s) => (
<Link key={s.type} href={`/services/website/samples/${s.type}`} style={{ textDecoration: 'none' }}>
<div className="ws-sample-card">
<div style={{ position: 'relative' }}>
<SampleMiniPreview type={s.type} />
<div style={{ position: 'absolute', top: 12, left: 12, display: 'flex', gap: 5, zIndex: 10 }}>
{s.tags.map((tag) => (
<span key={tag} style={{
fontSize: 10, fontWeight: 600, color: '#e2e8f0',
background: 'rgba(0,0,0,0.52)', backdropFilter: 'blur(8px)',
border: '1px solid rgba(255,255,255,0.13)',
padding: '2px 8px', borderRadius: 100,
}}>{tag}</span>
))}
</div>
<div style={{
position: 'absolute', bottom: 12, right: 12, zIndex: 10,
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(8px)',
border: `1px solid ${s.accent}45`,
borderRadius: 8, padding: '5px 12px',
fontSize: 11, color: s.accent, fontWeight: 700,
}}>
</div>
</div>
<div style={{ padding: '18px 22px 22px' }}>
<div style={{ fontSize: 11, color: '#334155', marginBottom: 5, letterSpacing: '0.05em' }}>
{s.subtitle}
</div>
<div style={{ fontSize: 16, fontWeight: 700, color: 'white', marginBottom: 8, letterSpacing: '-0.01em' }}>
{s.title}
</div>
<div style={{ fontSize: 13, color: '#475569', lineHeight: 1.65, wordBreak: 'keep-all' }}>
{s.desc}
</div>
</div>
</div>
</Link>
))}
</div>
</div>
</section>
{/* ── Process ── */}
<section style={{ padding: '56px 24px', background: 'rgba(255,255,255,0.015)', borderTop: '1px solid rgba(255,255,255,0.04)', borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
<div ref={processRef} className="ws-reveal" style={{ maxWidth: 1060, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 44 }}>
<p style={{ fontSize: 11, color: '#6366f1', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12, fontWeight: 700 }}>Process</p>
<h2 style={{ fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10, letterSpacing: '-0.02em' }}>
</h2>
<p style={{ color: '#475569', fontSize: 14 }}> 5 </p>
</div>
<div className="ws-process-steps" style={{ display: 'flex', alignItems: 'stretch', flexWrap: 'wrap', justifyContent: 'center', gap: 0 }}>
{processSteps.map((p, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center' }}>
<div className="ws-step-card" style={{
textAlign: 'center', padding: '28px 22px', minWidth: 138,
background: '#080d1a', borderRadius: 16,
border: '1px solid rgba(255,255,255,0.05)',
}}>
<div style={{ fontSize: 22, fontWeight: 800, color: '#6366f1', fontFamily: 'monospace', marginBottom: 12, letterSpacing: '-0.02em' }}>
{p.step}
</div>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', marginBottom: 6, wordBreak: 'keep-all' }}>
{p.title}
</div>
<div style={{ fontSize: 11, color: '#334155', lineHeight: 1.55, wordBreak: 'keep-all' }}>
{p.desc}
</div>
</div>
{i < processSteps.length - 1 && (
<div className="ws-process-divider" style={{ color: '#1e293b', fontSize: 20, padding: '0 4px', flexShrink: 0 }}></div>
)}
</div>
))}
</div>
</div>
</section>
{/* ── Pricing ── */}
<section style={{ padding: '64px 24px', maxWidth: 1040, margin: '0 auto' }}>
<div ref={pricingRef} className="ws-reveal">
<div style={{ textAlign: 'center', marginBottom: 44 }}>
<p style={{ fontSize: 11, color: '#6366f1', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12, fontWeight: 700 }}>Pricing</p>
<h2 style={{ fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 10, letterSpacing: '-0.02em' }}>
</h2>
<p style={{ color: '#475569', fontSize: 14 }}> </p>
</div>
<div className="ws-pricing-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(270px, 1fr))', gap: 20 }}>
{plans.map((plan) => (
<div key={plan.name} className="ws-plan-card" style={{
padding: 32, borderRadius: 20,
background: plan.featured ? '#0d1240' : '#080d1a',
border: `1px solid ${plan.featured ? plan.color + '40' : 'rgba(255,255,255,0.05)'}`,
position: 'relative', overflow: 'hidden',
boxShadow: plan.featured ? `0 24px 64px ${plan.color}12` : 'none',
}}>
{plan.featured && (
<div style={{
position: 'absolute', top: 20, right: 20,
background: plan.color, color: '#1e1b4b',
fontSize: 10, fontWeight: 800, padding: '3px 10px', borderRadius: 100,
}}>BEST</div>
)}
<div style={{ fontSize: 12, color: plan.color, fontWeight: 700, marginBottom: 12, letterSpacing: '0.05em' }}>
{plan.name}
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 4 }}>
<span style={{ fontSize: 40, fontWeight: 800, color: 'white', letterSpacing: '-0.03em' }}>{plan.price}</span>
<span style={{ fontSize: 15, color: '#64748b' }}>{plan.unit}</span>
</div>
<div style={{ fontSize: 12, color: '#334155', marginBottom: 24, wordBreak: 'keep-all' }}>{plan.note}</div>
<div style={{ borderTop: '1px solid rgba(255,255,255,0.06)', paddingTop: 20, marginBottom: 24 }}>
{plan.features.map((f) => (
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 12 }}>
<span style={{ color: plan.color, fontSize: 13, flexShrink: 0, fontWeight: 700 }}></span>
<span style={{ fontSize: 13, color: '#94a3b8', wordBreak: 'keep-all' }}>{f}</span>
</div>
))}
</div>
<button
onClick={() => openModal(`홈페이지 제작 - ${plan.name}`)}
style={{
display: 'block', width: '100%', textAlign: 'center', padding: '13px',
background: plan.featured ? plan.color : 'rgba(255,255,255,0.04)',
borderRadius: 10, color: plan.featured ? '#1e1b4b' : '#94a3b8',
fontWeight: 700, fontSize: 14, border: plan.featured ? 'none' : '1px solid rgba(255,255,255,0.07)',
transition: 'opacity 0.2s', cursor: 'pointer',
}}
>
</button>
</div>
))}
</div>
</div>
</section>
{/* ── FAQ ── */}
<section style={{ padding: '0 24px 64px', maxWidth: 720, margin: '0 auto' }}>
<div ref={faqRef} className="ws-reveal">
<div style={{ textAlign: 'center', marginBottom: 36 }}>
<p style={{ fontSize: 11, color: '#6366f1', letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12, fontWeight: 700 }}>FAQ</p>
<h2 style={{ fontSize: 28, fontWeight: 800, color: 'white', letterSpacing: '-0.02em' }}>
</h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{faqs.map((faq, i) => (
<div key={i} className="ws-faq-item" style={{
background: '#080d1a',
border: `1px solid ${openFaq === i ? 'rgba(99,102,241,0.4)' : 'rgba(255,255,255,0.05)'}`,
}}>
<button onClick={() => setOpenFaq(openFaq === i ? null : i)} style={{
width: '100%', textAlign: 'left', padding: '18px 22px',
background: 'none', border: 'none', cursor: 'pointer',
display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12,
}}>
<span style={{ fontSize: 14, fontWeight: 600, color: 'white', wordBreak: 'keep-all' }}>
{faq.q}
</span>
<span style={{
color: '#6366f1', fontSize: 22, flexShrink: 0,
transition: 'transform 0.25s',
transform: openFaq === i ? 'rotate(45deg)' : 'none',
display: 'inline-block',
}}>+</span>
</button>
{openFaq === i && (
<div style={{ padding: '0 22px 18px', fontSize: 14, color: '#475569', lineHeight: 1.8, wordBreak: 'keep-all' }}>
{faq.a}
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* ── CTA ── */}
<section style={{ padding: '0 24px 80px', textAlign: 'center' }}>
<div ref={ctaRef} className="ws-reveal">
<div className="ws-cta-box" style={{
maxWidth: 640, margin: '0 auto',
padding: '56px 44px', borderRadius: 24,
background: '#04102b',
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.012) 0px, rgba(255,255,255,0.012) 1px, transparent 1px, transparent 40px)',
border: '1px solid rgba(99,102,241,0.3)',
boxShadow: '0 24px 80px rgba(0,0,0,0.3)',
}}>
<h2 style={{ fontSize: 28, fontWeight: 800, color: 'white', marginBottom: 14, letterSpacing: '-0.02em', wordBreak: 'keep-all' }}>
?
</h2>
<p style={{ color: '#94a3b8', fontSize: 15, lineHeight: 1.75, marginBottom: 32, wordBreak: 'keep-all' }}>
24 .<br/>
···, .
</p>
<Link href="/freelance?service=website" style={{
display: 'inline-block', padding: '15px 40px',
background: '#6366f1',
borderRadius: 12, color: 'white', fontWeight: 700, fontSize: 15,
textDecoration: 'none',
boxShadow: '0 8px 24px rgba(99,102,241,0.4)',
transition: 'transform 0.3s, box-shadow 0.3s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)'; (e.currentTarget as HTMLElement).style.boxShadow = '0 16px 40px rgba(99,102,241,0.5)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = ''; (e.currentTarget as HTMLElement).style.boxShadow = '0 8px 24px rgba(99,102,241,0.4)'; }}>
</Link>
</div>
</div>
</section>
{/* ── 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: '5.5rem', right: '2rem', zIndex: 200,
width: 48, height: 48, borderRadius: '50%',
background: '#6366f1',
border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 8px 32px rgba(99,102,241,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="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="18 15 12 9 6 15"/>
</svg>
</button>
</div>
);
}

View File

@@ -1,539 +0,0 @@
import Link from 'next/link';
const menuItems = [
{ name: '버터 크루아상', price: '3,200', tag: '인기', tagColor: '#dc2626', desc: '프랑스산 에슈레 버터만 사용한 겹겹이 살아있는 크루아상', ingredients: ['에슈레 버터', '천연 발효', 'T65 밀가루'] },
{ name: '소금빵', price: '2,800', tag: '베스트', tagColor: '#d97706', desc: '오키나와 천연 소금과 발효 버터가 만나는 완벽한 짭조름함', ingredients: ['오키나와 소금', '발효 버터', '홋카이도 밀'] },
{ name: '딸기 쇼트케이크', price: '7,500', tag: '신메뉴', tagColor: '#7c3aed', desc: '국내산 딸기와 생크림이 만나는 클래식 케이크', ingredients: ['국내산 딸기', '생크림', '제노아즈'] },
{ name: '캄파뉴', price: '8,900', tag: '장인', tagColor: '#065f46', desc: '72시간 저온 발효로 만든 시큼하고 깊은 맛의 통밀빵', ingredients: ['통밀', '사워도우', '72h 발효'] },
];
const reviews = [
{ name: '김민서', rating: 5, text: '매일 아침 출근 전 들르게 되는 마법 같은 빵집. 크루아상은 그냥 인생 최고입니다.', date: '2024.08', verified: true },
{ name: '이준혁', rating: 5, text: '파리 유학시절 먹던 그 맛과 거의 흡사합니다. 국내에 이런 퀄리티가 있다니 놀랍습니다.', date: '2024.07', verified: true },
{ name: '박지은', rating: 5, text: '웨딩 케이크 주문했는데 하객들이 케이크 맛있다고 난리났어요. 다음에도 꼭 맡기겠습니다!', date: '2024.06', verified: true },
];
const specials = [
{ season: '봄', item: '딸기 타르틀렛', desc: '4월 한정', color: '#fda4af' },
{ season: '여름', item: '레몬 틸레', desc: '78월 한정', color: '#fde68a' },
{ season: '가을', item: '밤 크루아상', desc: '10월 한정', color: '#d97706' },
{ season: '겨울', item: '뱅쇼 브리오슈', desc: '12월 한정', color: '#c4b5fd' },
];
const hours = [
{ day: '월~금', time: '07:00 20:00' },
{ day: '토요일', time: '07:00 21:00' },
{ day: '일·공휴일', time: '09:00 18:00' },
];
export default function BakerySample() {
return (
<div style={{ background: '#fffbf5', minHeight: '100vh', color: '#1c1008' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400;1,700&family=Noto+Sans+KR:wght@300;400;500;600;700&display=swap');
@keyframes fadeUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
@keyframes float { 0%, 100% { transform: translateY(0px) rotate(-1deg); } 50% { transform: translateY(-12px) rotate(1deg); } }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2deg); } }
.menu-card:hover { transform: translateY(-8px); box-shadow: 0 32px 80px rgba(120,53,15,0.15) !important; }
.menu-card { transition: transform 0.4s cubic-bezier(.23,1,.32,1), box-shadow 0.4s; }
.review-card:hover { border-color: #fbbf24 !important; }
.review-card { transition: border-color 0.3s; }
.bk-btn-primary:hover { background: #92400e !important; transform: translateY(-1px); box-shadow: 0 12px 32px rgba(180,83,9,0.45) !important; }
.bk-btn-primary { transition: background 0.2s, transform 0.2s, box-shadow 0.2s; }
.bk-btn-ghost:hover { background: rgba(180,83,9,0.06) !important; }
.bk-btn-ghost { transition: background 0.2s; }
.special-card:hover { transform: scale(1.04); }
.special-card { transition: transform 0.3s; }
.ingredient-tag { background: rgba(180,83,9,0.07); color: #92400e; border: 1px solid rgba(180,83,9,0.15); border-radius: 100px; padding: 2px 10px; font-size: 11px; font-family: 'Noto Sans KR', sans-serif; white-space: nowrap; }
`}</style>
{/* Back Banner */}
<div style={{ background: 'linear-gradient(135deg, #1e1b4b, #312e81)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12 }}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif" }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#fcd34d', fontSize: 12, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{
background: 'rgba(255,251,245,0.96)', backdropFilter: 'blur(16px)',
borderBottom: '1px solid #fde8c8',
padding: '0 48px', height: 68,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
position: 'sticky', top: 0, zIndex: 100,
}}>
{/* Logo with wheat SVG */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<ellipse cx="14" cy="8" rx="5" ry="7" fill="#d97706" opacity="0.9" />
<ellipse cx="8" cy="14" rx="5" ry="4" fill="#b45309" opacity="0.7" transform="rotate(-30 8 14)" />
<ellipse cx="20" cy="14" rx="5" ry="4" fill="#b45309" opacity="0.7" transform="rotate(30 20 14)" />
<line x1="14" y1="28" x2="14" y2="8" stroke="#78350f" strokeWidth="1.5" strokeLinecap="round" />
</svg>
<div>
<div style={{ fontSize: 19, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', color: '#78350f', fontWeight: 700, lineHeight: 1 }}>
Le Petit Fort
</div>
<div style={{ fontSize: 10, color: '#c9a87c', letterSpacing: '0.18em', textTransform: 'uppercase', fontFamily: "'Noto Sans KR', sans-serif" }}>
Artisan Boulangerie
</div>
</div>
</div>
<div style={{ display: 'flex', gap: 28 }}>
{['메뉴', '스토리', '시즌 메뉴', '매장안내', '예약'].map((item) => (
<span key={item} style={{ fontSize: 14, color: '#78350f', cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 500 }}>{item}</span>
))}
</div>
<button className="bk-btn-primary" style={{
background: '#b45309', color: 'white', border: 'none',
padding: '9px 22px', borderRadius: 24, fontSize: 13,
fontWeight: 600, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}>
</button>
</nav>
{/* Hero */}
<section style={{
background: 'linear-gradient(160deg, #fef3c7 0%, #fde68a 40%, #fbbf24 75%, #d97706 100%)',
padding: '80px 48px', position: 'relative', overflow: 'hidden', minHeight: 500,
display: 'flex', alignItems: 'center',
}}>
{/* Decorative circles */}
<div style={{ position: 'absolute', right: -80, top: -80, width: 480, height: 480, borderRadius: '50%', background: 'rgba(180,83,9,0.07)' }} />
<div style={{ position: 'absolute', right: 60, bottom: -60, width: 280, height: 280, borderRadius: '50%', background: 'rgba(180,83,9,0.1)' }} />
{/* SVG Croissant illustration */}
<div style={{ position: 'absolute', right: '8%', top: '50%', transform: 'translateY(-50%)', animation: 'float 5s ease-in-out infinite' }}>
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" style={{ filter: 'drop-shadow(0 20px 40px rgba(120,53,15,0.3))' }}>
{/* Croissant shape */}
<path d="M90 30 C50 30, 20 60, 20 90 C20 110, 30 130, 50 145 C65 155, 75 158, 90 155 C105 158, 115 155, 130 145 C150 130, 160 110, 160 90 C160 60, 130 30, 90 30Z" fill="#d97706" />
<path d="M90 40 C55 40, 30 65, 30 90 C30 108, 38 124, 55 136 C68 145, 78 148, 90 145 C102 148, 112 145, 125 136 C142 124, 150 108, 150 90 C150 65, 125 40, 90 40Z" fill="#b45309" />
{/* Layered flakes */}
<path d="M60 80 Q90 70 120 80" stroke="#fde68a" strokeWidth="2.5" strokeLinecap="round" opacity="0.6" />
<path d="M55 92 Q90 82 125 92" stroke="#fef3c7" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
<path d="M60 104 Q90 94 120 104" stroke="#fde68a" strokeWidth="2" strokeLinecap="round" opacity="0.4" />
{/* Shine */}
<ellipse cx="70" cy="65" rx="15" ry="8" fill="white" opacity="0.15" transform="rotate(-20 70 65)" />
{/* Steam */}
<path d="M75 28 Q78 20 75 12" stroke="#fbbf24" strokeWidth="2" strokeLinecap="round" opacity="0.4" />
<path d="M90 24 Q93 14 90 5" stroke="#fbbf24" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
<path d="M105 28 Q108 20 105 12" stroke="#fbbf24" strokeWidth="2" strokeLinecap="round" opacity="0.4" />
</svg>
</div>
{/* Wheat decoration top right */}
<svg width="80" height="120" viewBox="0 0 80 120" fill="none" style={{ position: 'absolute', right: '28%', top: 20, opacity: 0.15, animation: 'sway 4s ease-in-out infinite' }}>
<line x1="40" y1="120" x2="40" y2="0" stroke="#78350f" strokeWidth="2" />
{[15, 35, 55, 75].map((y, i) => (
<g key={y}>
<ellipse cx="28" cy={y} rx="10" ry="5" fill="#78350f" transform={`rotate(-40 28 ${y})`} />
<ellipse cx="52" cy={y} rx="10" ry="5" fill="#78350f" transform={`rotate(40 52 ${y})`} />
</g>
))}
</svg>
<div style={{ maxWidth: 560, animation: 'fadeUp 0.8s ease forwards', position: 'relative', zIndex: 1 }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'rgba(255,255,255,0.5)', backdropFilter: 'blur(8px)', borderRadius: 100, padding: '5px 14px', marginBottom: 20 }}>
<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="#16a34a" /><circle cx="5" cy="5" r="2" fill="white" /></svg>
<span style={{ fontSize: 12, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 600 }}> 4 </span>
</div>
<div style={{ fontSize: 13, color: '#78350f', fontFamily: 'Playfair Display, serif', fontStyle: 'italic', marginBottom: 14, letterSpacing: '0.05em' }}>
&ldquo; , &rdquo;
</div>
<h1 style={{ fontFamily: 'Playfair Display, serif', fontSize: 'clamp(40px, 5vw, 64px)', fontWeight: 700, color: '#451a03', lineHeight: 1.15, marginBottom: 18 }}>
<br />
<span style={{ color: '#b45309' }}> </span><br />
</h1>
<p style={{ color: '#78350f', fontSize: 16, lineHeight: 1.85, marginBottom: 32, fontFamily: "'Noto Sans KR', sans-serif" }}>
4<br /> .
</p>
{/* Social proof */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 28 }}>
<div style={{ display: 'flex' }}>
{['#d97706', '#b45309', '#92400e', '#78350f'].map((c, i) => (
<div key={i} style={{ width: 30, height: 30, borderRadius: '50%', background: c, border: '2px solid white', marginLeft: i > 0 ? -8 : 0 }} />
))}
</div>
<div>
<div style={{ display: 'flex', gap: 2 }}>
{[1,2,3,4,5].map(i => (
<svg key={i} width="12" height="12" viewBox="0 0 12 12"><path d="M6 1l1.3 2.6L10 4.1 8 6.1l.5 2.9L6 7.6 3.5 9l.5-2.9-2-2 2.7-.5z" fill="#d97706" /></svg>
))}
</div>
<span style={{ fontSize: 12, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif" }}>4.9 · 1,200+ </span>
</div>
</div>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<button className="bk-btn-primary" style={{
background: '#b45309', color: 'white', border: 'none',
padding: '14px 32px', borderRadius: 28, fontSize: 15, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 24px rgba(180,83,9,0.35)',
}}>
</button>
<button className="bk-btn-ghost" style={{
background: 'rgba(255,255,255,0.5)', color: '#78350f',
border: '1px solid rgba(180,83,9,0.25)',
padding: '14px 32px', borderRadius: 28, fontSize: 15, fontWeight: 600,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
backdropFilter: 'blur(8px)',
}}>
</button>
</div>
</div>
</section>
{/* Season Special Banner */}
<div style={{ background: '#451a03', padding: '14px 48px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 32 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M8 1l1.8 3.6L14 5.6l-3 2.9.7 4.1L8 10.5 4.3 12.6l.7-4.1L2 5.6l4.2-.6z" fill="#fbbf24" /></svg>
<span style={{ color: '#fde68a', fontSize: 13, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', fontWeight: 600 }}>Spring Limited</span>
</div>
<span style={{ color: '#78350f' }}>|</span>
<span style={{ color: 'white', fontSize: 14, fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 600 }}>
🌸 4
</span>
<span style={{ color: '#78350f' }}>|</span>
<span style={{ color: '#fcd34d', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif", cursor: 'pointer', textDecoration: 'underline' }}> </span>
</div>
{/* Menu */}
<section style={{ padding: '80px 48px', background: '#fffbf5' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 52 }}>
<div style={{ fontSize: 11, color: '#b45309', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 12 }}>
Today&apos;s Menu
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 40, color: '#451a03', marginBottom: 12 }}>
</h2>
<p style={{ color: '#a78060', fontSize: 15, fontFamily: "'Noto Sans KR', sans-serif" }}>
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: 24 }}>
{menuItems.map((item) => (
<div key={item.name} className="menu-card" style={{
background: 'white', borderRadius: 20, border: '1px solid #fde8c8', overflow: 'hidden',
boxShadow: '0 4px 20px rgba(120,53,15,0.06)',
}}>
{/* Card visual — SVG pattern instead of emoji */}
<div style={{ height: 150, background: 'linear-gradient(135deg, #fef3c7, #fde68a)', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative', overflow: 'hidden' }}>
{/* Subtle dotted pattern */}
<svg width="100%" height="100%" style={{ position: 'absolute', inset: 0 }}>
<pattern id={`dots-${item.name}`} x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="1.5" fill="rgba(180,83,9,0.1)" />
</pattern>
<rect width="100%" height="100%" fill={`url(#dots-${item.name})`} />
</svg>
{/* Bread silhouette SVG */}
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" style={{ position: 'relative', zIndex: 1, filter: 'drop-shadow(0 4px 12px rgba(120,53,15,0.25))' }}>
<ellipse cx="40" cy="45" rx="30" ry="18" fill="#d97706" />
<ellipse cx="40" cy="38" rx="24" ry="16" fill="#b45309" />
<path d="M20 42 Q40 30 60 42" stroke="#fde68a" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
<path d="M24 48 Q40 38 56 48" stroke="#fef3c7" strokeWidth="1.5" strokeLinecap="round" opacity="0.4" />
<ellipse cx="30" cy="32" rx="8" ry="5" fill="white" opacity="0.1" transform="rotate(-20 30 32)" />
</svg>
<span style={{
position: 'absolute', top: 12, right: 12,
background: item.tagColor, color: 'white',
fontSize: 10, fontWeight: 700, padding: '3px 9px', borderRadius: 100,
fontFamily: "'Noto Sans KR', sans-serif",
}}>{item.tag}</span>
</div>
<div style={{ padding: '18px 20px' }}>
<div style={{ fontSize: 17, fontWeight: 700, color: '#451a03', fontFamily: 'Playfair Display, serif', marginBottom: 6 }}>
{item.name}
</div>
<div style={{ fontSize: 13, color: '#a78060', lineHeight: 1.65, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 12 }}>
{item.desc}
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
{item.ingredients.map(ig => (
<span key={ig} className="ingredient-tag">{ig}</span>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 19, fontWeight: 700, color: '#b45309', fontFamily: 'Playfair Display, serif' }}>
{item.price}
</span>
<button style={{
background: '#b45309', color: 'white', border: 'none',
padding: '7px 16px', borderRadius: 12, fontSize: 12,
fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
}}></button>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Seasonal Specials */}
<section style={{ padding: '72px 48px', background: '#fef9f0', borderTop: '1px solid #fde8c8' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 44 }}>
<div style={{ fontSize: 11, color: '#b45309', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 10 }}>
Seasonal Specials
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 36, color: '#451a03' }}>
</h2>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}>
{specials.map((s) => (
<div key={s.season} className="special-card" style={{
borderRadius: 16, padding: '28px 20px', textAlign: 'center',
background: 'white', border: '1px solid #fde8c8',
cursor: 'pointer', overflow: 'hidden', position: 'relative',
}}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 4, background: s.color, borderRadius: '16px 16px 0 0' }} />
<div style={{
width: 48, height: 48, borderRadius: '50%', margin: '0 auto 14px',
background: s.color + '30', display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<div style={{ width: 20, height: 20, borderRadius: '50%', background: s.color }} />
</div>
<div style={{ fontSize: 12, color: '#a78060', fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 8 }}>{s.season} </div>
<div style={{ fontSize: 16, fontWeight: 700, color: '#451a03', fontFamily: 'Playfair Display, serif', marginBottom: 6 }}>{s.item}</div>
<div style={{ fontSize: 11, color: '#b45309', fontWeight: 600, fontFamily: "'Noto Sans KR', sans-serif", background: s.color + '25', borderRadius: 100, padding: '2px 10px', display: 'inline-block' }}>{s.desc}</div>
</div>
))}
</div>
</div>
</section>
{/* Story */}
<section style={{ padding: '80px 48px', background: '#fffbf5', borderTop: '1px solid #fde8c8' }}>
<div style={{ maxWidth: 960, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 64, alignItems: 'center' }}>
<div>
<div style={{ fontSize: 11, color: '#b45309', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 14 }}>
Our Story
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 36, color: '#451a03', lineHeight: 1.3, marginBottom: 22 }}>
2009<br /> <br />
<em> </em>
</h2>
<p style={{ color: '#78350f', fontSize: 15, lineHeight: 1.9, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 16 }}>
5 . , .
</p>
<p style={{ color: '#78350f', fontSize: 15, lineHeight: 1.9, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 28 }}>
, , , . .
</p>
<div style={{ display: 'flex', gap: 32 }}>
{[{ n: '15+', l: '년 경력' }, { n: '200+', l: '종류의 빵' }, { n: '4시', l: '매일 기상' }].map((s) => (
<div key={s.l}>
<div style={{ fontSize: 24, fontWeight: 800, color: '#b45309', fontFamily: 'Playfair Display, serif' }}>{s.n}</div>
<div style={{ fontSize: 12, color: '#a78060', fontFamily: "'Noto Sans KR', sans-serif" }}>{s.l}</div>
</div>
))}
</div>
</div>
{/* Chef card with SVG illustration */}
<div style={{ background: 'linear-gradient(135deg, #fde68a, #fbbf24)', borderRadius: 24, padding: '44px 32px', textAlign: 'center', border: '2px solid #fcd34d', position: 'relative', overflow: 'hidden' }}>
{/* Pattern overlay */}
<svg width="100%" height="100%" style={{ position: 'absolute', inset: 0, opacity: 0.07 }}>
<pattern id="chef-dots" x="0" y="0" width="24" height="24" patternUnits="userSpaceOnUse">
<circle cx="12" cy="12" r="2" fill="#78350f" />
</pattern>
<rect width="100%" height="100%" fill="url(#chef-dots)" />
</svg>
{/* Chef SVG illustration */}
<div style={{ position: 'relative', zIndex: 1 }}>
<div style={{ width: 96, height: 96, borderRadius: '50%', background: 'rgba(255,255,255,0.4)', margin: '0 auto 20px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '3px solid rgba(255,255,255,0.6)' }}>
<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
{/* Chef hat */}
<ellipse cx="28" cy="20" rx="16" ry="12" fill="white" />
<rect x="12" y="18" width="32" height="8" rx="2" fill="white" />
<ellipse cx="28" cy="18" rx="8" ry="5" fill="#f3f4f6" />
{/* Face */}
<circle cx="28" cy="34" r="12" fill="#fbbf24" />
<circle cx="24" cy="32" r="1.5" fill="#78350f" />
<circle cx="32" cy="32" r="1.5" fill="#78350f" />
<path d="M23 38 Q28 42 33 38" stroke="#78350f" strokeWidth="1.5" strokeLinecap="round" fill="none" />
</svg>
</div>
<div style={{ fontSize: 20, fontFamily: 'Playfair Display, serif', color: '#451a03', fontWeight: 700, marginBottom: 6 }}>
Chef Kim Dongwoo
</div>
<div style={{ fontSize: 13, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif", lineHeight: 1.7, marginBottom: 16 }}>
Le Cordon Bleu Paris <br />
2009
</div>
{/* Awards */}
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
{['미쉐린 추천', '블루리본', '로컬 레전드'].map(award => (
<span key={award} style={{
background: 'rgba(255,255,255,0.5)', color: '#78350f',
fontSize: 10, fontWeight: 700, padding: '3px 10px',
borderRadius: 100, border: '1px solid rgba(255,255,255,0.7)',
fontFamily: "'Noto Sans KR', sans-serif",
}}>{award}</span>
))}
</div>
</div>
</div>
</div>
</section>
{/* Customer Reviews */}
<section style={{ padding: '80px 48px', background: '#fef9f0', borderTop: '1px solid #fde8c8' }}>
<div style={{ maxWidth: 1000, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 48 }}>
<div style={{ fontSize: 11, color: '#b45309', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 10 }}>
Guest Reviews
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 36, color: '#451a03', marginBottom: 12 }}>
</h2>
<div style={{ display: 'flex', gap: 4, justifyContent: 'center', alignItems: 'center', marginBottom: 8 }}>
{[1,2,3,4,5].map(i => (
<svg key={i} width="18" height="18" viewBox="0 0 18 18"><path d="M9 1.5l2 4L16 7l-3.5 3.4.8 4.8L9 13l-4.3 2.3.8-4.8L2 7l5-.5z" fill="#d97706" /></svg>
))}
<span style={{ marginLeft: 8, fontSize: 15, color: '#78350f', fontFamily: 'Playfair Display, serif', fontWeight: 700 }}>4.9</span>
<span style={{ fontSize: 13, color: '#a78060', fontFamily: "'Noto Sans KR', sans-serif" }}>&nbsp;(1,243 )</span>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
{reviews.map((r) => (
<div key={r.name} className="review-card" style={{
background: 'white', borderRadius: 20, padding: '28px 24px',
border: '1px solid #fde8c8', boxShadow: '0 2px 12px rgba(120,53,15,0.04)',
}}>
{/* Stars */}
<div style={{ display: 'flex', gap: 3, marginBottom: 14 }}>
{[1,2,3,4,5].map(i => (
<svg key={i} width="13" height="13" viewBox="0 0 13 13"><path d="M6.5 1l1.5 3 3.3.5-2.4 2.3.6 3.3L6.5 9 3 10.1l.6-3.3L1.2 4.5 4.5 4z" fill="#d97706" /></svg>
))}
{r.verified && (
<span style={{ marginLeft: 6, fontSize: 10, color: '#16a34a', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 600 }}>
</span>
)}
</div>
{/* Quote SVG */}
<svg width="24" height="18" viewBox="0 0 24 18" fill="none" style={{ marginBottom: 10, opacity: 0.3 }}>
<path d="M0 18V10.5C0 4.7 3.5 1.3 10.5 0l1.5 2.5C8.3 3.7 6.3 5.7 6 9h4.5V18H0zm12 0V10.5C12 4.7 15.5 1.3 22.5 0L24 2.5C20.3 3.7 18.3 5.7 18 9h4.5V18H12z" fill="#b45309" />
</svg>
<p style={{ fontSize: 14, color: '#78350f', lineHeight: 1.75, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 18 }}>
{r.text}
</p>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'linear-gradient(135deg, #fde68a, #d97706)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontWeight: 700, color: '#451a03', fontFamily: 'Playfair Display, serif' }}>
{r.name[0]}
</div>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: '#451a03', fontFamily: "'Noto Sans KR', sans-serif" }}>{r.name}</div>
<div style={{ fontSize: 11, color: '#c9a87c', fontFamily: "'Noto Sans KR', sans-serif" }}>{r.date}</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Hours & Location */}
<section style={{ padding: '72px 48px', background: '#451a03' }}>
<div style={{ maxWidth: 960, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 64 }}>
<div>
<div style={{ fontSize: 11, color: '#fbbf24', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 16 }}>Hours</div>
<h3 style={{ fontFamily: 'Playfair Display, serif', fontSize: 28, color: 'white', marginBottom: 28 }}> </h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{hours.map((h) => (
<div key={h.day} style={{ display: 'flex', justifyContent: 'space-between', borderBottom: '1px solid rgba(255,255,255,0.08)', paddingBottom: 12 }}>
<span style={{ fontSize: 14, color: '#fde68a', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 600 }}>{h.day}</span>
<span style={{ fontSize: 14, color: '#fcd34d', fontFamily: 'Playfair Display, serif' }}>{h.time}</span>
</div>
))}
</div>
<div style={{ marginTop: 20, padding: '12px 16px', background: 'rgba(251,191,36,0.1)', borderRadius: 10, border: '1px solid rgba(251,191,36,0.2)' }}>
<p style={{ fontSize: 13, color: '#fde68a', fontFamily: "'Noto Sans KR', sans-serif", lineHeight: 1.6 }}>
.<br />
.
</p>
</div>
</div>
<div>
<div style={{ fontSize: 11, color: '#fbbf24', fontWeight: 700, letterSpacing: '0.22em', textTransform: 'uppercase', fontFamily: 'Playfair Display, serif', marginBottom: 16 }}>Location</div>
<h3 style={{ fontFamily: 'Playfair Display, serif', fontSize: 28, color: 'white', marginBottom: 20 }}> </h3>
<p style={{ color: '#fde68a', fontSize: 15, lineHeight: 1.7, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 20 }}>
224-14<br /> 68
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{[
{ icon: 'M', text: '2호선 홍대입구역 3번 출구 도보 5분' },
{ icon: 'T', text: '02-334-5678' },
{ icon: '@', text: '@lepetitfort_seoul' },
].map((info) => (
<div key={info.text} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: '50%', background: 'rgba(251,191,36,0.15)', border: '1px solid rgba(251,191,36,0.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700, color: '#fbbf24', fontFamily: 'Playfair Display, serif', flexShrink: 0 }}>
{info.icon}
</div>
<span style={{ color: '#fcd34d', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>{info.text}</span>
</div>
))}
</div>
</div>
</div>
</section>
{/* CTA — Custom Cake */}
<section style={{ background: 'linear-gradient(135deg, #fef3c7, #fde68a)', padding: '72px 48px', textAlign: 'center' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: 'rgba(180,83,9,0.1)', borderRadius: 100, padding: '5px 14px', marginBottom: 20 }}>
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 1l1.3 2.6L10 4.1 8 6.1l.5 2.9L6 7.6 3.5 9l.5-2.9-2-2 2.7-.5z" fill="#b45309" /></svg>
<span style={{ fontSize: 12, color: '#b45309', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 700 }}>Custom Order</span>
</div>
<h2 style={{ fontFamily: 'Playfair Display, serif', fontSize: 38, color: '#451a03', marginBottom: 14 }}>
</h2>
<p style={{ color: '#78350f', fontSize: 16, lineHeight: 1.75, marginBottom: 12, fontFamily: "'Noto Sans KR', sans-serif" }}>
, , .<br />
3 .
</p>
<p style={{ color: '#a78060', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 32 }}>
· ·
</p>
<button className="bk-btn-primary" style={{
background: '#b45309', color: 'white', border: 'none',
padding: '15px 44px', borderRadius: 28, fontSize: 16, fontWeight: 700,
cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif",
boxShadow: '0 8px 28px rgba(180,83,9,0.4)',
}}>
</button>
</section>
{/* Footer */}
<footer style={{ background: '#1c1008', padding: '28px 48px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ color: '#78350f', fontSize: 14, fontFamily: 'Playfair Display, serif', fontStyle: 'italic', marginBottom: 4 }}>
Le Petit Fort Artisan Boulangerie
</div>
<div style={{ color: '#3c1a08', fontSize: 12, fontFamily: "'Noto Sans KR', sans-serif" }}>
© 2024 . All rights reserved.
</div>
</div>
<div style={{ display: 'flex', gap: 16 }}>
{['Instagram', 'Naver Map', 'Kakao'].map((s) => (
<span key={s} style={{ fontSize: 12, color: '#78350f', fontFamily: "'Noto Sans KR', sans-serif", cursor: 'pointer' }}>{s}</span>
))}
</div>
</footer>
</div>
);
}

View File

@@ -1,279 +0,0 @@
import Link from 'next/link';
export default function CorporateSample() {
const services = [
{
title: 'IT 인프라 구축',
desc: '기업 맞춤형 서버 환경 설계부터 클라우드 마이그레이션까지, 안정적인 IT 기반을 구축합니다.',
detail: '온프레미스 · 하이브리드 클라우드 · AWS · Azure',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 28, height: 28 }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 12H3a9 9 0 1018 0h-2M12 3v4m0 10v4M4.22 4.22l2.83 2.83m9.9 9.9l2.83 2.83M3 12h.01M20.99 12H21M4.22 19.78l2.83-2.83m9.9-9.9l2.83-2.83" />
</svg>
),
},
{
title: '사이버 보안 솔루션',
desc: '최신 위협에 대응하는 엔터프라이즈급 보안 시스템. 침해사고 예방부터 대응까지 통합 관리합니다.',
detail: 'ISMS · 취약점 분석 · 보안 모니터링 · 컴플라이언스',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 28, height: 28 }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
</svg>
),
},
{
title: '디지털 전환 (DX)',
desc: '레거시 시스템을 현대화하고 비즈니스 프로세스를 자동화하는 DX 컨설팅을 제공합니다.',
detail: 'ERP 연동 · 프로세스 자동화 · 데이터 시각화 · AI 도입',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5} style={{ width: 28, height: 28 }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5m.75-9l3-3 2.148 2.148A12.061 12.061 0 0116.5 7.605" />
</svg>
),
},
];
const stats = [
{ num: '15+', label: '년 업력' },
{ num: '340+', label: '완료 프로젝트' },
{ num: '180+', label: '기업 고객사' },
{ num: '99.9%', label: '서비스 가동률' },
];
const certs = ['AWS Partner', 'ISO 27001', 'ISMS-P 인증', 'Microsoft Partner', 'Google Cloud'];
const clients = ['삼성전자', 'LG유플러스', '현대모비스', 'SK하이닉스', 'KT', '신한은행', 'NH농협', '롯데정보통신'];
const testimonials = [
{
quote: '마이그레이션 기간 동안 단 한 번의 서비스 중단도 없었습니다. 완벽한 이전이었습니다.',
name: '이○○ CTO',
company: '핀테크 스타트업',
},
{
quote: '보안 감사 이후 취약점 제로. 컴플라이언스 대응이 이렇게 빠를 수 있다는 게 놀라웠습니다.',
name: '박○○ IT팀장',
company: '중견 제조사',
},
];
return (
<div style={{ background: '#ffffff', minHeight: '100vh', color: '#1e293b', fontFamily: 'system-ui, sans-serif' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700;800&family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
@keyframes fadeInUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
@keyframes floatDot { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@keyframes gridPan { from { background-position: 0 0; } to { background-position: 60px 60px; } }
.corp-card:hover { transform: translateY(-5px); box-shadow: 0 24px 64px rgba(29,78,216,0.1); }
.corp-card { transition: transform 0.3s, box-shadow 0.3s; }
.corp-client:hover { background: #eff6ff !important; border-color: #bfdbfe !important; color: #1d4ed8 !important; }
.corp-client { transition: all 0.2s; }
.corp-cert:hover { border-color: #93c5fd !important; }
.corp-cert { transition: border-color 0.2s; }
.corp-btn-primary:hover { background: #1d4ed8 !important; box-shadow: 0 8px 24px rgba(29,78,216,0.4) !important; }
.corp-btn-primary { transition: all 0.2s; }
`}</style>
{/* Back Banner */}
<div style={{ background: 'linear-gradient(135deg, #1e1b4b, #312e81)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12 }}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: "'Noto Sans KR', sans-serif", display: 'flex', alignItems: 'center', gap: 6 }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#6366f1', fontSize: 12, fontFamily: 'Montserrat, sans-serif', fontWeight: 700 }}>SAMPLE · </span>
</div>
{/* Navbar */}
<nav style={{ background: '#fff', borderBottom: '1px solid #e2e8f0', padding: '0 48px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: 68, position: 'sticky', top: 0, zIndex: 100, boxShadow: '0 2px 16px rgba(0,0,0,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 38, height: 38, borderRadius: 10, background: 'linear-gradient(135deg, #1d4ed8, #1e40af)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: 16, fontWeight: 800, fontFamily: 'Montserrat, sans-serif' }}>T</div>
<div>
<div style={{ fontSize: 15, fontWeight: 800, color: '#0f172a', fontFamily: 'Montserrat, sans-serif', lineHeight: 1 }}>TechSolution</div>
<div style={{ fontSize: 10, color: '#94a3b8', letterSpacing: '0.1em', fontFamily: 'Montserrat, sans-serif' }}>ENTERPRISE IT</div>
</div>
</div>
<div style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
{['회사소개', '서비스', '포트폴리오', '고객사', '채용', '연락처'].map((item) => (
<span key={item} style={{ fontSize: 13, color: '#475569', cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif", fontWeight: 500 }}>{item}</span>
))}
<button className="corp-btn-primary" style={{ background: '#2563eb', color: 'white', border: 'none', padding: '9px 22px', borderRadius: 8, fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif", boxShadow: '0 4px 16px rgba(37,99,235,0.3)' }}>
</button>
</div>
</nav>
{/* Hero */}
<section style={{ background: 'linear-gradient(150deg, #0a192f 0%, #0d2757 50%, #0a2060 100%)', padding: '96px 48px 80px', position: 'relative', overflow: 'hidden' }}>
{/* Animated grid */}
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(59,130,246,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(59,130,246,0.08) 1px, transparent 1px)', backgroundSize: '60px 60px', animation: 'gridPan 12s linear infinite' }} />
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 70% 60% at 75% 50%, rgba(59,130,246,0.12) 0%, transparent 70%)' }} />
{/* Floating tech nodes */}
{[
{ x: '68%', y: '20%', delay: '0s', size: 6, color: '#60a5fa' },
{ x: '78%', y: '55%', delay: '1s', size: 4, color: '#a78bfa' },
{ x: '85%', y: '35%', delay: '2s', size: 8, color: '#34d399' },
{ x: '72%', y: '72%', delay: '0.5s', size: 5, color: '#60a5fa' },
].map((dot, i) => (
<div key={i} style={{ position: 'absolute', left: dot.x, top: dot.y, width: dot.size, height: dot.size, borderRadius: '50%', background: dot.color, boxShadow: `0 0 12px ${dot.color}`, animation: `floatDot 3s ease-in-out ${dot.delay} infinite` }} />
))}
{/* SVG Tech Illustration */}
<svg style={{ position: 'absolute', right: 60, top: '50%', transform: 'translateY(-50%)', width: 320, height: 280, opacity: 0.12 }} viewBox="0 0 320 280">
<rect x="40" y="40" width="100" height="70" rx="8" stroke="#60a5fa" strokeWidth="1.5" fill="none"/>
<rect x="180" y="40" width="100" height="70" rx="8" stroke="#a78bfa" strokeWidth="1.5" fill="none"/>
<rect x="110" y="170" width="100" height="70" rx="8" stroke="#34d399" strokeWidth="1.5" fill="none"/>
<line x1="140" y1="110" x2="160" y2="170" stroke="#60a5fa" strokeWidth="1" strokeDasharray="4 4"/>
<line x1="180" y1="110" x2="160" y2="170" stroke="#a78bfa" strokeWidth="1" strokeDasharray="4 4"/>
<circle cx="90" cy="75" r="16" stroke="#60a5fa" strokeWidth="1" fill="rgba(96,165,250,0.1)"/>
<circle cx="230" cy="75" r="16" stroke="#a78bfa" strokeWidth="1" fill="rgba(167,139,250,0.1)"/>
<circle cx="160" cy="205" r="20" stroke="#34d399" strokeWidth="1" fill="rgba(52,211,153,0.1)"/>
</svg>
<div style={{ maxWidth: 680, position: 'relative', animation: 'fadeInUp 0.8s ease forwards' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'rgba(96,165,250,0.1)', border: '1px solid rgba(96,165,250,0.25)', padding: '6px 16px', borderRadius: 4, marginBottom: 28, fontFamily: 'Montserrat, sans-serif' }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#34d399', boxShadow: '0 0 8px rgba(52,211,153,0.8)' }} />
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', color: '#60a5fa', textTransform: 'uppercase' }}>Enterprise IT Solutions</span>
</div>
<h1 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 'clamp(34px, 4vw, 54px)', fontWeight: 800, color: 'white', lineHeight: 1.2, marginBottom: 20 }}>
<br />
<span style={{ background: 'linear-gradient(90deg, #60a5fa, #a78bfa)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}> </span><br />
</h1>
<p style={{ color: '#94a3b8', fontSize: 16, lineHeight: 1.85, marginBottom: 36, fontFamily: "'Noto Sans KR', sans-serif" }}>
15 IT .<br />
, , .
</p>
<div style={{ display: 'flex', gap: 14, flexWrap: 'wrap' }}>
<button className="corp-btn-primary" style={{ background: '#2563eb', color: 'white', border: 'none', padding: '14px 32px', borderRadius: 8, fontSize: 15, fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif", boxShadow: '0 8px 24px rgba(37,99,235,0.35)' }}>
</button>
<button style={{ background: 'transparent', color: '#cbd5e1', border: '1px solid rgba(255,255,255,0.18)', padding: '14px 32px', borderRadius: 8, fontSize: 15, fontWeight: 600, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif" }}>
</button>
</div>
</div>
</section>
{/* Stats Bar */}
<section style={{ background: '#1d4ed8', padding: '36px 48px' }}>
<div style={{ maxWidth: 1100, margin: '0 auto', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0 }}>
{stats.map((s, i) => (
<div key={i} style={{ textAlign: 'center', padding: '12px 24px', borderRight: i < 3 ? '1px solid rgba(255,255,255,0.2)' : 'none' }}>
<div style={{ fontSize: 38, fontWeight: 800, color: 'white', fontFamily: 'Montserrat, sans-serif', lineHeight: 1 }}>{s.num}</div>
<div style={{ fontSize: 13, color: '#bfdbfe', fontFamily: "'Noto Sans KR', sans-serif", marginTop: 6 }}>{s.label}</div>
</div>
))}
</div>
</section>
{/* Certifications */}
<section style={{ background: '#f8fafc', padding: '28px 48px', borderBottom: '1px solid #e2e8f0' }}>
<div style={{ maxWidth: 1100, margin: '0 auto', display: 'flex', alignItems: 'center', gap: 24, flexWrap: 'wrap', justifyContent: 'center' }}>
<span style={{ fontSize: 11, fontWeight: 700, color: '#94a3b8', letterSpacing: '0.15em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', flexShrink: 0 }}> · </span>
{certs.map((c) => (
<div key={c} className="corp-cert" style={{ padding: '8px 18px', border: '1px solid #e2e8f0', borderRadius: 6, background: 'white', fontSize: 12, fontWeight: 700, color: '#475569', fontFamily: 'Montserrat, sans-serif', letterSpacing: '0.03em' }}>
{c}
</div>
))}
</div>
</section>
{/* Services */}
<section style={{ padding: '88px 48px', background: 'white' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 56 }}>
<div style={{ fontSize: 11, color: '#2563eb', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>Our Services</div>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 36, fontWeight: 800, color: '#0f172a', marginBottom: 14 }}> </h2>
<p style={{ color: '#64748b', fontSize: 16, fontFamily: "'Noto Sans KR', sans-serif" }}> IT </p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 28 }}>
{services.map((svc, i) => (
<div key={svc.title} className="corp-card" style={{ padding: 36, borderRadius: 18, background: 'white', border: '1px solid #e2e8f0', boxShadow: '0 4px 24px rgba(0,0,0,0.05)', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: ['linear-gradient(90deg, #2563eb, #60a5fa)', 'linear-gradient(90deg, #7c3aed, #a78bfa)', 'linear-gradient(90deg, #059669, #34d399)'][i] }} />
<div style={{ width: 56, height: 56, borderRadius: 14, background: ['#eff6ff', '#f5f3ff', '#f0fdf4'][i], border: `1px solid ${['#bfdbfe', '#ddd6fe', '#bbf7d0'][i]}`, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 22, color: ['#2563eb', '#7c3aed', '#059669'][i] }}>
{svc.icon}
</div>
<h3 style={{ fontSize: 19, fontWeight: 700, color: '#0f172a', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>{svc.title}</h3>
<p style={{ fontSize: 14, color: '#64748b', lineHeight: 1.8, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 16 }}>{svc.desc}</p>
<div style={{ padding: '10px 14px', background: '#f8fafc', borderRadius: 8, fontSize: 12, color: '#94a3b8', fontFamily: 'Montserrat, sans-serif', letterSpacing: '0.02em' }}>{svc.detail}</div>
<div style={{ marginTop: 20, color: '#2563eb', fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: 'Montserrat, sans-serif', display: 'flex', alignItems: 'center', gap: 6 }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
</div>
</div>
))}
</div>
</div>
</section>
{/* Testimonials */}
<section style={{ padding: '80px 48px', background: 'linear-gradient(135deg, #f0f7ff, #f8f5ff)' }}>
<div style={{ maxWidth: 900, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 48 }}>
<div style={{ fontSize: 11, color: '#2563eb', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 12 }}>Client Reviews</div>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 32, fontWeight: 800, color: '#0f172a' }}> </h2>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 }}>
{testimonials.map((t, i) => (
<div key={i} style={{ background: 'white', borderRadius: 16, padding: '32px 28px', border: '1px solid #e2e8f0', boxShadow: '0 4px 20px rgba(0,0,0,0.05)' }}>
<div style={{ fontSize: 32, color: '#bfdbfe', fontFamily: 'Georgia, serif', lineHeight: 1, marginBottom: 16 }}>"</div>
<p style={{ fontSize: 15, color: '#475569', lineHeight: 1.8, fontFamily: "'Noto Sans KR', sans-serif", marginBottom: 20 }}>{t.quote}</p>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'linear-gradient(135deg, #2563eb, #7c3aed)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: 15, fontWeight: 700 }}>
{t.name[0]}
</div>
<div>
<div style={{ fontSize: 13, fontWeight: 700, color: '#0f172a', fontFamily: "'Noto Sans KR', sans-serif" }}>{t.name}</div>
<div style={{ fontSize: 12, color: '#94a3b8', fontFamily: "'Noto Sans KR', sans-serif" }}>{t.company}</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Clients */}
<section style={{ padding: '72px 48px', background: 'white' }}>
<div style={{ maxWidth: 1100, margin: '0 auto', textAlign: 'center' }}>
<div style={{ fontSize: 11, color: '#94a3b8', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 36 }}>Trusted By Leading Companies</div>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 12 }}>
{clients.map((c) => (
<div key={c} className="corp-client" style={{ padding: '14px 28px', borderRadius: 10, border: '1px solid #e2e8f0', background: '#f8fafc', fontSize: 14, fontWeight: 600, color: '#475569', fontFamily: "'Noto Sans KR', sans-serif", cursor: 'pointer' }}>{c}</div>
))}
</div>
</div>
</section>
{/* Contact CTA */}
<section style={{ background: 'linear-gradient(150deg, #0a192f, #0d2757)', padding: '88px 48px', textAlign: 'center' }}>
<div style={{ maxWidth: 600, margin: '0 auto' }}>
<div style={{ fontSize: 11, color: '#60a5fa', fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', fontFamily: 'Montserrat, sans-serif', marginBottom: 20 }}>Get Started</div>
<h2 style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 38, fontWeight: 800, color: 'white', marginBottom: 16, lineHeight: 1.2 }}>
프로젝트를 시작하세요
</h2>
<p style={{ color: '#94a3b8', fontSize: 16, lineHeight: 1.8, marginBottom: 36, fontFamily: "'Noto Sans KR', sans-serif" }}>
IT 솔루션이 필요하시면 언제든지 연락해주세요.<br />
전담 컨설턴트가 최적의 방안을 제안해드립니다.
</p>
<div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
<button className="corp-btn-primary" style={{ background: '#2563eb', color: 'white', border: 'none', padding: '15px 40px', borderRadius: 8, fontSize: 15, fontWeight: 700, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif", boxShadow: '0 8px 24px rgba(37,99,235,0.4)' }}>
무료 상담 신청
</button>
<button style={{ background: 'transparent', color: '#cbd5e1', border: '1px solid rgba(255,255,255,0.18)', padding: '15px 40px', borderRadius: 8, fontSize: 15, fontWeight: 600, cursor: 'pointer', fontFamily: "'Noto Sans KR', sans-serif" }}>
02-1234-5678
</button>
</div>
</div>
</section>
{/* Footer */}
<footer style={{ background: '#020817', padding: '28px 48px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
<div style={{ color: '#334155', fontSize: 13, fontFamily: "'Noto Sans KR', sans-serif" }}>© 2024 . All rights reserved.</div>
<div style={{ color: '#1e293b', fontSize: 12, fontFamily: 'Montserrat, sans-serif' }}> 123 15F</div>
</footer>
</div>
);
}

View File

@@ -1,436 +0,0 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
const kpis = [
{ label: '월 활성 사용자', value: '124,832', change: '+12.4%', up: true, color: '#3b82f6', sparkline: [40, 55, 45, 70, 60, 85, 100] },
{ label: '월 매출', value: '₩284M', change: '+8.7%', up: true, color: '#10b981', sparkline: [50, 60, 55, 75, 80, 78, 100] },
{ label: '전환율', value: '3.62%', change: '-0.3%', up: false, color: '#f59e0b', sparkline: [90, 80, 85, 75, 70, 72, 68] },
{ label: '고객 만족도', value: '4.8', change: '+0.2', up: true, color: '#8b5cf6', sparkline: [70, 72, 74, 76, 78, 80, 85] },
];
const lineData = [
{ month: 'Jan', revenue: 65, users: 48 },
{ month: 'Feb', revenue: 78, users: 55 },
{ month: 'Mar', revenue: 72, users: 52 },
{ month: 'Apr', revenue: 89, users: 64 },
{ month: 'May', revenue: 95, users: 71 },
{ month: 'Jun', revenue: 82, users: 66 },
{ month: 'Jul', revenue: 110, users: 88 },
{ month: 'Aug', revenue: 128, users: 100 },
];
const activities = [
{ user: 'lee@company.com', action: '프리미엄 플랜 구독', time: '2분 전', status: 'success', avatar: 'L' },
{ user: 'park@startup.io', action: 'API 한도 초과 경고', time: '14분 전', status: 'warning', avatar: 'P' },
{ user: 'kim@corp.kr', action: '팀 멤버 5명 초대', time: '31분 전', status: 'info', avatar: 'K' },
{ user: 'choi@brand.com', action: '결제 실패 (카드 만료)', time: '1시간 전', status: 'error', avatar: 'C' },
{ user: 'jung@agency.co', action: '새 워크스페이스 생성', time: '2시간 전', status: 'success', avatar: 'J' },
];
const menus = [
{ id: 'overview', label: 'Overview', dot: '#3b82f6' },
{ id: 'analytics', label: 'Analytics', dot: '#10b981' },
{ id: 'users', label: 'Users', dot: null },
{ id: 'revenue', label: 'Revenue', dot: null },
{ id: 'reports', label: 'Reports', dot: null },
{ id: 'settings', label: 'Settings', dot: null },
];
const channels = [
{ label: 'Organic Search', val: 78, color: '#3b82f6' },
{ label: 'Direct', val: 55, color: '#10b981' },
{ label: 'Social Media', val: 42, color: '#a855f7' },
{ label: 'Email', val: 34, color: '#f59e0b' },
{ label: 'Referral', val: 20, color: '#ec4899' },
];
const alerts = [
{ type: 'error', msg: 'API 응답 지연 (p99 > 2s)', time: '5분 전' },
{ type: 'warning', msg: '스토리지 사용량 85% 초과', time: '32분 전' },
{ type: 'success', msg: '일일 백업 완료', time: '1시간 전' },
];
const statusColor: Record<string, string> = { success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' };
function SparkLine({ data, color }: { data: number[]; color: string }) {
const max = Math.max(...data);
const min = Math.min(...data);
const h = 28;
const w = 72;
const step = w / (data.length - 1);
const pts = data.map((v, i) => {
const x = i * step;
const y = h - ((v - min) / (max - min || 1)) * h;
return `${x},${y}`;
}).join(' ');
return (
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
<polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" opacity="0.7" />
<polyline points={`${pts} ${w},${h} 0,${h}`} fill={color} fillOpacity="0.08" stroke="none" />
</svg>
);
}
function LineChart({ data }: { data: typeof lineData }) {
const chartH = 130;
const chartW = 440;
const padL = 32;
const padB = 24;
const innerW = chartW - padL;
const innerH = chartH - padB;
const maxRev = Math.max(...data.map(d => d.revenue));
const revPts = data.map((d, i) => {
const x = padL + (i / (data.length - 1)) * innerW;
const y = (1 - d.revenue / maxRev) * innerH;
return `${x},${y}`;
}).join(' ');
const usrPts = data.map((d, i) => {
const x = padL + (i / (data.length - 1)) * innerW;
const y = (1 - d.users / maxRev) * innerH;
return `${x},${y}`;
}).join(' ');
return (
<svg width="100%" viewBox={`0 0 ${chartW} ${chartH}`} preserveAspectRatio="xMidYMid meet">
{/* Grid lines */}
{[0, 0.25, 0.5, 0.75, 1].map((t, i) => (
<g key={i}>
<line x1={padL} y1={t * innerH} x2={chartW} y2={t * innerH} stroke="rgba(255,255,255,0.04)" strokeWidth="1" />
<text x={padL - 4} y={t * innerH + 4} textAnchor="end" fontSize="8" fill="#1e3a5f">
{Math.round(maxRev * (1 - t))}
</text>
</g>
))}
{/* Area fills */}
<defs>
<linearGradient id="grad-rev" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#3b82f6" stopOpacity="0.25" />
<stop offset="100%" stopColor="#3b82f6" stopOpacity="0" />
</linearGradient>
<linearGradient id="grad-usr" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#10b981" stopOpacity="0.2" />
<stop offset="100%" stopColor="#10b981" stopOpacity="0" />
</linearGradient>
</defs>
<polyline points={`${revPts} ${chartW},${innerH} ${padL},${innerH}`} fill="url(#grad-rev)" stroke="none" />
<polyline points={`${usrPts} ${chartW},${innerH} ${padL},${innerH}`} fill="url(#grad-usr)" stroke="none" />
{/* Lines */}
<polyline points={revPts} fill="none" stroke="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<polyline points={usrPts} fill="none" stroke="#10b981" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" strokeDasharray="4 2" />
{/* Dots at last point */}
{[{ pts: revPts, color: '#3b82f6' }, { pts: usrPts, color: '#10b981' }].map(({ pts: p, color }) => {
const last = p.split(' ').pop()!;
const [lx, ly] = last.split(',').map(Number);
return <circle key={color} cx={lx} cy={ly} r="3.5" fill={color} stroke="#0f172a" strokeWidth="2" />;
})}
{/* X axis labels */}
{data.map((d, i) => (
<text key={i} x={padL + (i / (data.length - 1)) * innerW} y={innerH + 16} textAnchor="middle" fontSize="8" fill="#334155">{d.month}</text>
))}
</svg>
);
}
export default function DashboardSample() {
const [activeMenu, setActiveMenu] = useState('overview');
const [alertsVisible, setAlertsVisible] = useState(true);
return (
<div style={{ background: '#0a0f1e', minHeight: '100vh', color: 'white' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.dash-menu-item:hover { background: rgba(255,255,255,0.05) !important; }
.dash-menu-item { transition: background 0.15s; }
.dash-kpi:hover { border-color: rgba(255,255,255,0.1) !important; transform: translateY(-2px); }
.dash-kpi { transition: border-color 0.2s, transform 0.2s; cursor: default; }
.dash-row:hover { background: rgba(255,255,255,0.03) !important; }
.dash-row { transition: background 0.15s; }
.quick-action:hover { background: rgba(59,130,246,0.12) !important; border-color: rgba(59,130,246,0.3) !important; }
.quick-action { transition: background 0.2s, border-color 0.2s; cursor: pointer; }
`}</style>
{/* Back Banner */}
<div style={{ background: 'linear-gradient(135deg, #1e1b4b, #312e81)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12, position: 'relative', zIndex: 200 }}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: 'DM Sans, sans-serif' }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#38bdf8', fontSize: 12, fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>
SAMPLE ·
</span>
</div>
<div style={{ display: 'flex', height: 'calc(100vh - 40px)' }}>
{/* Sidebar */}
<aside style={{ width: 220, background: '#060b18', borderRight: '1px solid rgba(255,255,255,0.05)', display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
{/* Logo */}
<div style={{ padding: '18px 18px 14px', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 34, height: 34, borderRadius: 9, background: 'linear-gradient(135deg, #3b82f6, #06b6d4)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{/* DF logo SVG */}
<svg width="16" height="14" viewBox="0 0 16 14" fill="none">
<rect x="0" y="0" width="7" height="6" rx="1" fill="white" opacity="0.9" />
<rect x="0" y="8" width="7" height="6" rx="1" fill="white" opacity="0.6" />
<rect x="9" y="0" width="7" height="14" rx="1" fill="white" opacity="0.4" />
</svg>
</div>
<div>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}>DataFlow</div>
<div style={{ fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace' }}>v2.4.1 · PRO</div>
</div>
</div>
</div>
{/* Quick Actions */}
<div style={{ padding: '12px 10px 8px' }}>
<div style={{ fontSize: 9, color: '#1e3a5f', fontFamily: 'DM Mono, monospace', letterSpacing: '0.15em', padding: '4px 10px 6px', textTransform: 'uppercase' }}>QUICK ACTIONS</div>
<div style={{ display: 'flex', gap: 6, padding: '0 2px' }}>
{[
{ label: 'Export', color: '#3b82f6' },
{ label: 'Invite', color: '#10b981' },
{ label: 'Alert', color: '#f59e0b' },
].map(a => (
<button key={a.label} className="quick-action" style={{ flex: 1, padding: '6px 4px', borderRadius: 6, border: '1px solid rgba(255,255,255,0.05)', background: 'transparent', color: a.color, fontSize: 10, fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>
{a.label}
</button>
))}
</div>
</div>
{/* Nav */}
<nav style={{ flex: 1, padding: '4px 10px', overflowY: 'auto' }}>
<div style={{ fontSize: 9, color: '#1e3a5f', fontFamily: 'DM Mono, monospace', letterSpacing: '0.15em', padding: '8px 10px 4px', textTransform: 'uppercase' }}>MAIN</div>
{menus.map((m) => (
<button key={m.id} className="dash-menu-item" onClick={() => setActiveMenu(m.id)} style={{
width: '100%', textAlign: 'left', padding: '9px 12px', borderRadius: 8, border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 2,
background: activeMenu === m.id ? 'rgba(59,130,246,0.15)' : 'transparent',
color: activeMenu === m.id ? '#60a5fa' : '#475569',
}}>
{/* Menu icon SVG */}
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style={{ flexShrink: 0 }}>
<rect x="1" y="1" width="5" height="5" rx="1.5" fill={activeMenu === m.id ? '#60a5fa' : '#1e3a5f'} />
<rect x="8" y="1" width="5" height="5" rx="1.5" fill={activeMenu === m.id ? '#60a5fa' : '#1e3a5f'} opacity="0.6" />
<rect x="1" y="8" width="5" height="5" rx="1.5" fill={activeMenu === m.id ? '#60a5fa' : '#1e3a5f'} opacity="0.6" />
<rect x="8" y="8" width="5" height="5" rx="1.5" fill={activeMenu === m.id ? '#60a5fa' : '#1e3a5f'} opacity="0.4" />
</svg>
<span style={{ fontSize: 13, fontWeight: activeMenu === m.id ? 600 : 400, fontFamily: 'DM Sans, sans-serif', flex: 1 }}>
{m.label}
</span>
{m.dot && <div style={{ width: 4, height: 4, borderRadius: '50%', background: m.dot, animation: 'pulse-dot 2s infinite' }} />}
</button>
))}
</nav>
{/* User */}
<div style={{ padding: '12px 14px', borderTop: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: '50%', background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700 }}>A</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: '#e2e8f0', fontFamily: 'DM Sans, sans-serif', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>Admin</div>
<div style={{ fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace' }}>Super Admin</div>
</div>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#10b981', flexShrink: 0 }} />
</div>
</div>
</aside>
{/* Main */}
<main style={{ flex: 1, overflowY: 'auto', animation: 'fadeIn 0.4s ease' }}>
{/* Top bar */}
<div style={{ padding: '14px 24px', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#080d1a', position: 'sticky', top: 0, zIndex: 10 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ position: 'relative' }}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }}>
<circle cx="7" cy="7" r="5" stroke="#334155" strokeWidth="1.5" />
<line x1="11" y1="11" x2="14" y2="14" stroke="#334155" strokeWidth="1.5" strokeLinecap="round" />
</svg>
<input placeholder="검색..." style={{ background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)', color: '#94a3b8', padding: '7px 12px 7px 32px', borderRadius: 8, fontSize: 12, fontFamily: 'DM Sans, sans-serif', outline: 'none', width: 180 }} />
</div>
</div>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<select style={{ background: '#0f172a', border: '1px solid rgba(255,255,255,0.07)', color: '#94a3b8', padding: '7px 12px', borderRadius: 8, fontSize: 12, fontFamily: 'DM Sans, sans-serif', cursor: 'pointer' }}>
<option> 30</option>
</select>
{/* Alert bell */}
<button onClick={() => setAlertsVisible(!alertsVisible)} style={{ position: 'relative', background: '#0f172a', border: '1px solid rgba(255,255,255,0.07)', padding: '7px 10px', borderRadius: 8, cursor: 'pointer', display: 'flex' }}>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M7 1.5C4.5 1.5 3 3.2 3 5.5v3L1.5 10.5h11L11 8.5v-3C11 3.2 9.5 1.5 7 1.5z" stroke="#94a3b8" strokeWidth="1.2" fill="none" />
<path d="M5.5 10.5a1.5 1.5 0 003 0" stroke="#94a3b8" strokeWidth="1.2" />
</svg>
<div style={{ position: 'absolute', top: 5, right: 5, width: 7, height: 7, borderRadius: '50%', background: '#ef4444', border: '1.5px solid #080d1a' }} />
</button>
<button style={{ background: '#3b82f6', border: 'none', color: 'white', padding: '7px 16px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'DM Sans, sans-serif' }}>
</button>
</div>
</div>
<div style={{ padding: '20px 24px' }}>
{/* Alerts panel */}
{alertsVisible && (
<div style={{ marginBottom: 20, padding: '14px 18px', borderRadius: 12, background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<div style={{ fontSize: 13, fontWeight: 600, color: 'white', fontFamily: 'DM Sans, sans-serif' }}> </div>
<button onClick={() => setAlertsVisible(false)} style={{ background: 'none', border: 'none', color: '#334155', fontSize: 11, cursor: 'pointer', fontFamily: 'DM Sans, sans-serif' }}></button>
</div>
<div style={{ display: 'flex', gap: 12 }}>
{alerts.map((a, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', borderRadius: 8, background: statusColor[a.type] + '10', border: `1px solid ${statusColor[a.type]}25`, flex: 1 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: statusColor[a.type], flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, color: '#e2e8f0', fontFamily: 'DM Sans, sans-serif', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.msg}</div>
<div style={{ fontSize: 10, color: '#334155', fontFamily: 'DM Mono, monospace' }}>{a.time}</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Page header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
<div>
<h1 style={{ fontFamily: 'DM Sans, sans-serif', fontSize: 22, fontWeight: 700, color: 'white', marginBottom: 2 }}>Overview</h1>
<div style={{ fontSize: 12, color: '#334155', fontFamily: 'DM Mono, monospace' }}>2024.08.14 · 10:32 </div>
</div>
</div>
{/* KPI Cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 14, marginBottom: 18 }}>
{kpis.map((kpi) => (
<div key={kpi.label} className="dash-kpi" style={{ padding: '16px 18px', borderRadius: 14, background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8 }}>
<div style={{ fontSize: 11, color: '#475569', fontFamily: 'DM Sans, sans-serif', fontWeight: 500, maxWidth: 80 }}>{kpi.label}</div>
<SparkLine data={kpi.sparkline} color={kpi.color} />
</div>
<div style={{ fontSize: 24, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif', marginBottom: 6 }}>{kpi.value}</div>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 3, background: kpi.up ? 'rgba(16,185,129,0.12)' : 'rgba(239,68,68,0.12)', borderRadius: 6, padding: '2px 7px', fontSize: 11, fontWeight: 700, color: kpi.up ? '#10b981' : '#ef4444', fontFamily: 'DM Mono, monospace' }}>
{kpi.up ? '↑' : '↓'} {kpi.change}
</div>
</div>
))}
</div>
{/* Charts row */}
<div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 14, marginBottom: 18 }}>
{/* Line Chart */}
<div style={{ padding: '20px 22px', borderRadius: 14, background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}> & </div>
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
<div style={{ width: 20, height: 2, background: '#3b82f6', borderRadius: 2 }} />
<span style={{ fontSize: 10, color: '#475569', fontFamily: 'DM Mono, monospace' }}></span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
<div style={{ width: 20, height: 2, background: '#10b981', borderRadius: 2, backgroundImage: 'repeating-linear-gradient(90deg, #10b981 0, #10b981 4px, transparent 4px, transparent 6px)' }} />
<span style={{ fontSize: 10, color: '#475569', fontFamily: 'DM Mono, monospace' }}></span>
</div>
<div style={{ display: 'flex', gap: 6 }}>
{['1M', '3M', '6M', '1Y'].map((p) => (
<button key={p} style={{ background: p === '6M' ? '#1e3a5f' : 'transparent', border: 'none', color: p === '6M' ? '#60a5fa' : '#334155', padding: '3px 7px', borderRadius: 5, fontSize: 10, cursor: 'pointer', fontFamily: 'DM Mono, monospace' }}>{p}</button>
))}
</div>
</div>
</div>
<LineChart data={lineData} />
</div>
{/* Channel Progress */}
<div style={{ padding: '20px 22px', borderRadius: 14, background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)' }}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif', marginBottom: 18 }}> </div>
{channels.map((p) => (
<div key={p.label} style={{ marginBottom: 13 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 5 }}>
<span style={{ fontSize: 12, color: '#94a3b8', fontFamily: 'DM Sans, sans-serif' }}>{p.label}</span>
<span style={{ fontSize: 11, color: p.color, fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>{p.val}%</span>
</div>
<div style={{ height: 4, background: '#1e293b', borderRadius: 2, overflow: 'hidden', position: 'relative' }}>
<div style={{ height: '100%', borderRadius: 2, background: `linear-gradient(90deg, ${p.color}90, ${p.color})`, width: `${p.val}%` }} />
</div>
</div>
))}
{/* Donut summary */}
<div style={{ marginTop: 18, display: 'flex', alignItems: 'center', gap: 14 }}>
<svg width="52" height="52" viewBox="0 0 52 52">
{(() => {
const total = channels.reduce((s, c) => s + c.val, 0);
let offset = 0;
const r = 20;
const circ = 2 * Math.PI * r;
return channels.map((c) => {
const dash = (c.val / total) * circ;
const el = (
<circle key={c.label} cx="26" cy="26" r={r} fill="none" stroke={c.color} strokeWidth="8"
strokeDasharray={`${dash} ${circ - dash}`}
strokeDashoffset={-offset}
transform="rotate(-90 26 26)"
style={{ transition: 'stroke-dasharray 0.5s' }} />
);
offset += dash;
return el;
});
})()}
<circle cx="26" cy="26" r="12" fill="#0f172a" />
<text x="26" y="29" textAnchor="middle" fontSize="8" fill="#60a5fa" fontWeight="700">ALL</text>
</svg>
<div style={{ fontSize: 11, color: '#475569', fontFamily: 'DM Sans, sans-serif', lineHeight: 1.6 }}>
Organic <br />
<span style={{ color: '#3b82f6', fontWeight: 700 }}> 78%</span><br />
</div>
</div>
</div>
</div>
{/* Activity Table */}
<div style={{ borderRadius: 14, background: '#0f172a', border: '1px solid rgba(255,255,255,0.06)', overflow: 'hidden' }}>
<div style={{ padding: '14px 18px', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontSize: 14, fontWeight: 700, color: 'white', fontFamily: 'DM Sans, sans-serif' }}> </div>
<button style={{ background: 'none', border: '1px solid rgba(255,255,255,0.08)', color: '#475569', padding: '5px 12px', borderRadius: 6, fontSize: 11, cursor: 'pointer', fontFamily: 'DM Sans, sans-serif' }}> </button>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
{['사용자', '활동', '시간', '상태'].map((h) => (
<th key={h} style={{ padding: '9px 18px', textAlign: 'left', fontSize: 9, color: '#334155', fontFamily: 'DM Mono, monospace', letterSpacing: '0.12em', textTransform: 'uppercase', fontWeight: 500 }}>{h}</th>
))}
</tr>
</thead>
<tbody>
{activities.map((a, i) => (
<tr key={i} className="dash-row" style={{ borderBottom: '1px solid rgba(255,255,255,0.025)' }}>
<td style={{ padding: '11px 18px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ width: 26, height: 26, borderRadius: '50%', background: `hsl(${i * 60}, 60%, 40%)`, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700, flexShrink: 0 }}>{a.avatar}</div>
<span style={{ fontFamily: 'DM Mono, monospace', fontSize: 11, color: '#60a5fa' }}>{a.user}</span>
</div>
</td>
<td style={{ padding: '11px 18px', fontFamily: 'DM Sans, sans-serif', fontSize: 13, color: '#94a3b8' }}>{a.action}</td>
<td style={{ padding: '11px 18px', fontFamily: 'DM Mono, monospace', fontSize: 11, color: '#334155' }}>{a.time}</td>
<td style={{ padding: '11px 18px' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: statusColor[a.status] + '12', borderRadius: 6, padding: '3px 8px' }}>
<div style={{ width: 5, height: 5, borderRadius: '50%', background: statusColor[a.status] }} />
<span style={{ fontSize: 10, color: statusColor[a.status], fontFamily: 'DM Mono, monospace', fontWeight: 500 }}>
{a.status === 'success' ? 'OK' : a.status === 'warning' ? 'WARN' : a.status === 'error' ? 'ERR' : 'INFO'}
</span>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
</div>
);
}

View File

@@ -1,428 +0,0 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
const rankings = [
{ rank: 1, name: 'ShadowViper_KR', score: 9_842, tier: 'GRANDMASTER', wins: 312, kda: '18.4', change: '+2' },
{ rank: 2, name: 'NightFalcon', score: 9_610, tier: 'GRANDMASTER', wins: 289, kda: '15.2', change: '0' },
{ rank: 3, name: 'Xenon_X', score: 9_241, tier: 'MASTER', wins: 267, kda: '12.9', change: '+1' },
{ rank: 4, name: 'KR_Dominator', score: 8_970, tier: 'MASTER', wins: 251, kda: '11.7', change: '-1' },
{ rank: 5, name: 'Pulse_Wave', score: 8_834, tier: 'DIAMOND', wins: 238, kda: '10.3', change: '+3' },
];
const modes = [
{ id: 'solo', name: 'SOLO', sub: '1 vs 1', desc: '순수한 실력으로 맞붙는 1대1 대결', color: '#06b6d4', players: '12,400', season: 'S7' },
{ id: 'duo', name: 'DUO', sub: '2 vs 2', desc: '파트너와 함께하는 전략적 팀플레이', color: '#a855f7', players: '28,700', season: 'S7' },
{ id: 'squad', name: 'SQUAD', sub: '5 vs 5', desc: '전략과 팀워크로 승리를 쟁취', color: '#f59e0b', players: '7,100', season: 'S7' },
];
const recentMatches = [
{ result: 'WIN', mode: 'DUO', duration: '18:32', kills: 12, deaths: 2, assists: 8, rating: '+32' },
{ result: 'WIN', mode: 'SOLO', duration: '22:11', kills: 8, deaths: 1, assists: 4, rating: '+24' },
{ result: 'LOSS', mode: 'SQUAD', duration: '31:45', kills: 5, deaths: 5, assists: 12, rating: '-18' },
{ result: 'WIN', mode: 'DUO', duration: '15:20', kills: 15, deaths: 3, assists: 6, rating: '+40' },
];
const champions = [
{ name: 'VIPER', role: 'Assassin', color: '#06b6d4', power: 92, winRate: '63%' },
{ name: 'KIRA', role: 'Support', color: '#ec4899', power: 78, winRate: '58%' },
{ name: 'IRON', role: 'Tank', color: '#94a3b8', power: 85, winRate: '55%' },
{ name: 'NOVA', role: 'Mage', color: '#a855f7', power: 88, winRate: '61%' },
];
const tierColor: Record<string, string> = {
GRANDMASTER: '#fbbf24',
MASTER: '#a855f7',
DIAMOND: '#60a5fa',
};
const tierIcon = (tier: string) => (
<svg width="14" height="16" viewBox="0 0 14 16" fill="none">
<path d="M7 0L9 4L13 5L10 8.5L11 13L7 11L3 13L4 8.5L1 5L5 4Z" fill={tierColor[tier] || '#6b7280'} opacity="0.9" />
<path d="M7 2L8.5 5L12 5.8L9.5 8.2L10.2 11.8L7 10L3.8 11.8L4.5 8.2L2 5.8L5.5 5Z" fill="white" opacity="0.2" />
</svg>
);
export default function GameSample() {
const [onlinePlayers, setOnlinePlayers] = useState(48_219);
const [matchingCount, setMatchingCount] = useState(1_342);
const [matchingActive, setMatchingActive] = useState(false);
const [matchTimer, setMatchTimer] = useState(0);
const [selectedChampion, setSelectedChampion] = useState(0);
const [seasonProgress] = useState(67);
useEffect(() => {
const interval = setInterval(() => {
setOnlinePlayers((p) => p + Math.floor(Math.random() * 6 - 2));
setMatchingCount((c) => c + Math.floor(Math.random() * 4 - 1));
}, 2000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
let timer: ReturnType<typeof setInterval>;
if (matchingActive) {
timer = setInterval(() => setMatchTimer((t) => t + 1), 1000);
} else {
setMatchTimer(0);
}
return () => clearInterval(timer);
}, [matchingActive]);
return (
<div style={{ background: '#000000', minHeight: '100vh', color: 'white', overflowX: 'hidden' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@400;500;600;700&display=swap');
@keyframes pulse-ring { 0% { transform: scale(0.97); box-shadow: 0 0 0 0 rgba(6,182,212,0.6); } 70% { transform: scale(1); box-shadow: 0 0 0 14px rgba(6,182,212,0); } 100% { transform: scale(0.97); } }
@keyframes scan { 0% { top: 0; } 100% { top: 100%; } }
@keyframes glitch { 0%, 90%, 100% { transform: none; clip-path: none; } 92% { transform: translate(-2px, 1px); clip-path: inset(30% 0 50% 0); } 94% { transform: translate(2px, -1px); clip-path: inset(60% 0 10% 0); } 96% { transform: translate(-1px, 0); clip-path: inset(10% 0 70% 0); } }
@keyframes neonFlicker { 0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% { opacity: 1; } 20%, 24%, 55% { opacity: 0.4; } }
@keyframes matchPulse { 0%, 100% { box-shadow: 0 0 20px rgba(6,182,212,0.4); } 50% { box-shadow: 0 0 50px rgba(6,182,212,0.9), 0 0 100px rgba(6,182,212,0.3); } }
@keyframes float-particle { 0%, 100% { transform: translateY(0) rotate(0deg); opacity: 0.6; } 50% { transform: translateY(-20px) rotate(180deg); opacity: 1; } }
@keyframes progress-fill { from { width: 0; } to { width: var(--w); } }
.mode-card:hover { border-color: var(--card-color) !important; transform: translateY(-4px); box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
.mode-card { transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s; }
.rank-row:hover { background: rgba(6,182,212,0.04) !important; }
.rank-row { transition: background 0.15s; }
.champ-card:hover { border-color: var(--champ-color) !important; transform: scale(1.04); }
.champ-card { transition: border-color 0.3s, transform 0.3s; cursor: pointer; }
.match-row:hover { background: rgba(255,255,255,0.03) !important; }
.match-row { transition: background 0.15s; }
`}</style>
{/* Back Banner */}
<div style={{ background: 'linear-gradient(135deg, #1e1b4b, #312e81)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12, position: 'relative', zIndex: 200 }}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: 'Rajdhani, sans-serif' }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#06b6d4', fontSize: 12, fontFamily: 'Orbitron, sans-serif', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Navbar */}
<nav style={{ position: 'sticky', top: 0, zIndex: 100, background: 'rgba(0,0,0,0.95)', backdropFilter: 'blur(20px)', borderBottom: '1px solid rgba(6,182,212,0.15)', padding: '0 48px', height: 60, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 18, fontWeight: 900, background: 'linear-gradient(90deg, #06b6d4, #a855f7)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', letterSpacing: '0.1em', animation: 'neonFlicker 8s infinite' }}>
NEXUS ARENA
</div>
<div style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
{['HOME', 'MATCH', 'RANK', 'SHOP', 'CLAN'].map((item) => (
<span key={item} style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 11, fontWeight: 700, color: '#1e3a5f', cursor: 'pointer', letterSpacing: '0.1em' }}>{item}</span>
))}
</div>
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
{/* Season pass mini */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: '#f59e0b', letterSpacing: '0.1em' }}>S7 PASS</span>
<div style={{ width: 60, height: 4, background: '#1a1a2e', borderRadius: 2, overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${seasonProgress}%`, background: 'linear-gradient(90deg, #f59e0b, #fbbf24)', borderRadius: 2 }} />
</div>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: '#f59e0b' }}>{seasonProgress}%</span>
</div>
<div style={{ width: 1, height: 20, background: 'rgba(6,182,212,0.2)' }} />
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#10b981', boxShadow: '0 0 8px rgba(16,185,129,0.8)' }} />
<span style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: '#10b981', fontWeight: 600 }}>
{onlinePlayers.toLocaleString()} ONLINE
</span>
</div>
</div>
</nav>
{/* Hero */}
<section style={{ minHeight: '88vh', position: 'relative', overflow: 'hidden', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '60px 48px', background: 'radial-gradient(ellipse 80% 70% at 50% 40%, rgba(6,182,212,0.09) 0%, rgba(168,85,247,0.06) 50%, transparent 100%)' }}>
{/* Grid */}
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(6,182,212,0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(6,182,212,0.05) 1px, transparent 1px)', backgroundSize: '60px 60px' }} />
{/* Scan */}
<div style={{ position: 'absolute', left: 0, right: 0, height: '1px', background: 'linear-gradient(90deg, transparent, rgba(6,182,212,0.5), transparent)', animation: 'scan 6s linear infinite', pointerEvents: 'none' }} />
{/* Floating particles */}
{[
{ left: '10%', top: '20%', size: 4, delay: '0s', color: '#06b6d4' },
{ left: '85%', top: '30%', size: 6, delay: '1s', color: '#a855f7' },
{ left: '20%', top: '70%', size: 3, delay: '2s', color: '#06b6d4' },
{ left: '75%', top: '65%', size: 5, delay: '0.5s', color: '#f59e0b' },
{ left: '50%', top: '15%', size: 3, delay: '1.5s', color: '#a855f7' },
{ left: '60%', top: '80%', size: 4, delay: '2.5s', color: '#10b981' },
].map((p, i) => (
<div key={i} style={{ position: 'absolute', left: p.left, top: p.top, width: p.size, height: p.size, borderRadius: '50%', background: p.color, boxShadow: `0 0 ${p.size * 3}px ${p.color}`, animation: `float-particle 4s ${p.delay} ease-in-out infinite` }} />
))}
{/* Corner brackets */}
{[
{ top: 40, left: 40, borderTop: '2px solid #06b6d4', borderLeft: '2px solid #06b6d4' },
{ top: 40, right: 40, borderTop: '2px solid #a855f7', borderRight: '2px solid #a855f7' },
{ bottom: 40, left: 40, borderBottom: '2px solid #06b6d4', borderLeft: '2px solid #06b6d4' },
{ bottom: 40, right: 40, borderBottom: '2px solid #a855f7', borderRight: '2px solid #a855f7' },
].map((s, i) => (
<div key={i} style={{ position: 'absolute', width: 40, height: 40, ...s }} />
))}
<div style={{ textAlign: 'center', position: 'relative', zIndex: 1 }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 11, fontWeight: 700, letterSpacing: '0.3em', color: '#06b6d4', marginBottom: 20, textTransform: 'uppercase' }}>
Season 7 · RANKED MATCH
</div>
<h1 style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 'clamp(48px, 8vw, 96px)', fontWeight: 900, lineHeight: 1.0, marginBottom: 16, background: 'linear-gradient(180deg, #ffffff 0%, rgba(255,255,255,0.55) 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', animation: 'glitch 12s infinite' }}>
NEXUS<br />
<span style={{ background: 'linear-gradient(90deg, #06b6d4, #a855f7)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>ARENA</span>
</h1>
<p style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 18, color: '#475569', letterSpacing: '0.05em', marginBottom: 44 }}>
ENTER THE ARENA. CLAIM YOUR GLORY.
</p>
{/* Live Stats */}
<div style={{ display: 'flex', gap: 24, justifyContent: 'center', marginBottom: 44 }}>
{[
{ label: 'ONLINE', val: onlinePlayers.toLocaleString(), color: '#06b6d4' },
{ label: 'IN MATCH', val: matchingCount.toLocaleString(), color: '#a855f7' },
{ label: 'SERVERS', val: '24', color: '#10b981' },
{ label: 'AVG WAIT', val: '< 30s', color: '#f59e0b' },
].map((s) => (
<div key={s.label} style={{ padding: '14px 20px', background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(8px)', border: `1px solid ${s.color}30`, borderTop: `2px solid ${s.color}`, textAlign: 'center', minWidth: 90 }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 22, fontWeight: 900, color: s.color }}>{s.val}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 10, color: '#334155', letterSpacing: '0.2em', marginTop: 4 }}>{s.label}</div>
</div>
))}
</div>
{/* Match button */}
{!matchingActive ? (
<button onClick={() => setMatchingActive(true)} style={{ background: 'linear-gradient(135deg, #06b6d4, #0891b2)', border: 'none', color: 'white', padding: '18px 56px', fontSize: 16, fontWeight: 900, cursor: 'pointer', fontFamily: 'Orbitron, sans-serif', letterSpacing: '0.1em', clipPath: 'polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px))', animation: 'pulse-ring 2s infinite' }}>
FIND MATCH
</button>
) : (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
<div style={{ padding: '18px 56px', border: '2px solid #06b6d4', color: '#06b6d4', fontFamily: 'Orbitron, sans-serif', fontSize: 16, fontWeight: 900, letterSpacing: '0.1em', animation: 'matchPulse 1.5s infinite', clipPath: 'polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px))' }}>
MATCHING... {String(Math.floor(matchTimer / 60)).padStart(2, '0')}:{String(matchTimer % 60).padStart(2, '0')}
</div>
<button onClick={() => setMatchingActive(false)} style={{ background: 'none', border: 'none', color: '#334155', fontFamily: 'Rajdhani, sans-serif', fontSize: 13, cursor: 'pointer', letterSpacing: '0.1em', textDecoration: 'underline' }}>
CANCEL SEARCH
</button>
</div>
)}
</div>
</section>
{/* Season Pass */}
<section style={{ padding: '40px 48px', background: 'rgba(0,0,0,0.9)', borderTop: '1px solid rgba(251,191,36,0.15)', borderBottom: '1px solid rgba(251,191,36,0.15)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 32 }}>
<div style={{ flexShrink: 0 }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 11, fontWeight: 700, color: '#f59e0b', letterSpacing: '0.2em', marginBottom: 4 }}>SEASON 7 PASS</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 28, fontWeight: 700, color: 'white' }}>
Lv. <span style={{ color: '#f59e0b' }}>42</span>
<span style={{ fontSize: 14, color: '#475569', marginLeft: 8 }}>/ 100</span>
</div>
</div>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: '#475569' }}>{seasonProgress}% </span>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 10, color: '#f59e0b' }}> 1,840 XP</span>
</div>
<div style={{ height: 8, background: '#0d0d1a', borderRadius: 4, overflow: 'hidden', border: '1px solid rgba(251,191,36,0.15)' }}>
<div style={{ height: '100%', width: `${seasonProgress}%`, background: 'linear-gradient(90deg, #d97706, #fbbf24, #f59e0b)', borderRadius: 4, boxShadow: '0 0 12px rgba(251,191,36,0.4)' }} />
</div>
</div>
<div style={{ display: 'flex', gap: 12, flexShrink: 0 }}>
{['스킨 3개', '이모트 5개', '칭호 2개', '골드 5,000'].map((reward) => (
<div key={reward} style={{ padding: '6px 12px', background: 'rgba(251,191,36,0.08)', border: '1px solid rgba(251,191,36,0.2)', borderRadius: 4 }}>
<span style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 11, color: '#fbbf24', fontWeight: 600 }}>{reward}</span>
</div>
))}
</div>
</div>
</div>
</section>
{/* Champion Select */}
<section style={{ padding: '60px 48px', background: '#000000' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 28 }}>
<div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: '#06b6d4', letterSpacing: '0.2em', marginBottom: 8 }}>// ROSTER</div>
<h2 style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 22, fontWeight: 900, letterSpacing: '0.05em', color: 'white' }}>
CHAMPIONS<span style={{ color: '#06b6d4' }}>.</span>
</h2>
</div>
<button style={{ background: 'none', border: '1px solid rgba(6,182,212,0.3)', color: '#06b6d4', padding: '7px 16px', borderRadius: 3, fontFamily: 'Orbitron, sans-serif', fontSize: 10, cursor: 'pointer', letterSpacing: '0.1em' }}>
ALL CHAMPIONS
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 14 }}>
{champions.map((c, i) => (
<div
key={c.name}
className="champ-card"
onClick={() => setSelectedChampion(i)}
// @ts-expect-error CSS variable
style={{ '--champ-color': c.color, border: `1px solid ${selectedChampion === i ? c.color : c.color + '25'}`, borderRadius: 6, padding: '22px 18px', background: selectedChampion === i ? c.color + '10' : 'rgba(0,0,0,0.6)', position: 'relative', overflow: 'hidden' }}
>
{selectedChampion === i && (
<div style={{ position: 'absolute', top: 8, right: 8 }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 8, color: c.color, border: `1px solid ${c.color}60`, padding: '1px 6px', borderRadius: 2, letterSpacing: '0.1em' }}>SELECTED</div>
</div>
)}
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: `linear-gradient(90deg, transparent, ${c.color}, transparent)` }} />
{/* Champion SVG silhouette */}
<div style={{ width: 56, height: 56, borderRadius: '50%', background: c.color + '20', border: `2px solid ${c.color}40`, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 14 }}>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<circle cx="14" cy="8" r="5" fill={c.color} opacity="0.8" />
<path d="M5 28C5 21 9 18 14 18C19 18 23 21 23 28" fill={c.color} opacity="0.6" />
<rect x="10" y="18" width="8" height="2" rx="1" fill={c.color} opacity="0.4" />
</svg>
</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 16, fontWeight: 900, color: 'white', marginBottom: 4 }}>{c.name}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: c.color, letterSpacing: '0.1em', fontWeight: 600, marginBottom: 14 }}>{c.role}</div>
<div style={{ marginBottom: 10 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 8, color: '#334155', letterSpacing: '0.1em' }}>POWER</span>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: c.color }}>{c.power}</span>
</div>
<div style={{ height: 3, background: '#0d0d1a', borderRadius: 2, overflow: 'hidden' }}>
<div style={{ height: '100%', background: c.color, width: `${c.power}%`, boxShadow: `0 0 6px ${c.color}` }} />
</div>
</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: '#475569' }}>
Win Rate: <span style={{ color: '#10b981', fontWeight: 700 }}>{c.winRate}</span>
</div>
</div>
))}
</div>
</div>
</section>
{/* Game Modes */}
<section style={{ padding: '56px 48px', background: 'rgba(0,0,0,0.85)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ textAlign: 'center', marginBottom: 36 }}>
<h2 style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 26, fontWeight: 900, color: 'white', letterSpacing: '0.05em', marginBottom: 8 }}>
GAME MODES
</h2>
<div style={{ width: 60, height: 2, background: 'linear-gradient(90deg, #06b6d4, #a855f7)', margin: '0 auto' }} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
{modes.map((mode) => (
<div key={mode.id} className="mode-card"
// @ts-expect-error CSS variable
style={{ '--card-color': mode.color, border: `1px solid ${mode.color}25`, borderRadius: 4, padding: '28px 24px', background: 'rgba(0,0,0,0.7)', cursor: 'pointer', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: `linear-gradient(90deg, transparent, ${mode.color}, transparent)` }} />
{/* Mode icon */}
<div style={{ width: 44, height: 44, borderRadius: '50%', background: mode.color + '15', border: `1px solid ${mode.color}35`, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
{mode.id === 'solo' && <polygon points="10,2 18,18 2,18" fill={mode.color} opacity="0.9" />}
{mode.id === 'duo' && <><circle cx="7" cy="10" r="5" fill={mode.color} opacity="0.9" /><circle cx="13" cy="10" r="5" fill={mode.color} opacity="0.5" /></>}
{mode.id === 'squad' && <><rect x="2" y="2" width="7" height="7" rx="2" fill={mode.color} opacity="0.9" /><rect x="11" y="2" width="7" height="7" rx="2" fill={mode.color} opacity="0.6" /><rect x="2" y="11" width="7" height="7" rx="2" fill={mode.color} opacity="0.6" /><rect x="11" y="11" width="7" height="7" rx="2" fill={mode.color} opacity="0.4" /></>}
</svg>
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 22, fontWeight: 900, color: 'white' }}>{mode.name}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: mode.color, fontWeight: 700, letterSpacing: '0.1em' }}>{mode.sub}</div>
</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 15, color: '#475569', lineHeight: 1.5, marginBottom: 20 }}>{mode.desc}</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 11, color: mode.color, letterSpacing: '0.08em' }}>{mode.players} IN QUEUE</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 10, color: '#1e3a5f', letterSpacing: '0.1em' }}>AVG WAIT: &lt; 30s</div>
</div>
<button style={{ background: `${mode.color}20`, border: `1px solid ${mode.color}50`, color: mode.color, padding: '7px 18px', borderRadius: 2, fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'Orbitron, sans-serif', letterSpacing: '0.08em' }}>
PLAY
</button>
</div>
</div>
))}
</div>
</div>
</section>
{/* Rankings + Recent Matches */}
<section style={{ padding: '56px 48px', background: '#000000' }}>
<div style={{ maxWidth: 1100, margin: '0 auto', display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: 24 }}>
{/* Rankings */}
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
<h2 style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 20, fontWeight: 900, letterSpacing: '0.05em', color: 'white' }}>
GLOBAL RANKING<span style={{ color: '#06b6d4' }}>.</span>
</h2>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 11, color: '#1e3a5f', letterSpacing: '0.1em' }}>Season 7 · Top 100</div>
</div>
<div style={{ border: '1px solid rgba(6,182,212,0.12)', borderRadius: 4, overflow: 'hidden' }}>
<div style={{ display: 'grid', gridTemplateColumns: '52px 1fr 88px 60px 60px', padding: '9px 16px', borderBottom: '1px solid rgba(6,182,212,0.1)', background: 'rgba(6,182,212,0.05)' }}>
{['RANK', 'PLAYER', 'SCORE', 'WINS', 'K/D/A'].map((h) => (
<div key={h} style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 8, color: '#06b6d4', letterSpacing: '0.15em' }}>{h}</div>
))}
</div>
{rankings.map((r, i) => (
<div key={i} className="rank-row" style={{ display: 'grid', gridTemplateColumns: '52px 1fr 88px 60px 60px', padding: '13px 16px', borderBottom: i < rankings.length - 1 ? '1px solid rgba(255,255,255,0.03)' : 'none', alignItems: 'center' }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 18, fontWeight: 900, color: i === 0 ? '#fbbf24' : i === 1 ? '#9ca3af' : i === 2 ? '#cd7c2f' : '#1e3a5f' }}>
{r.rank < 10 ? `0${r.rank}` : r.rank}
</div>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
{tierIcon(r.tier)}
<span style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 14, fontWeight: 700, color: 'white' }}>{r.name}</span>
</div>
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 7, color: tierColor[r.tier] || '#6b7280', border: `1px solid ${tierColor[r.tier] || '#6b7280'}40`, padding: '1px 5px', letterSpacing: '0.08em' }}>{r.tier}</span>
<span style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: r.change.startsWith('+') ? '#10b981' : r.change === '0' ? '#334155' : '#ef4444' }}>
{r.change === '0' ? '' : r.change}
</span>
</div>
</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 13, fontWeight: 700, color: '#06b6d4' }}>{r.score.toLocaleString()}</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 13, color: '#475569', fontWeight: 600 }}>{r.wins}</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 12, color: '#10b981' }}>{r.kda}</div>
</div>
))}
</div>
</div>
{/* Recent Matches */}
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
<h2 style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 20, fontWeight: 900, letterSpacing: '0.05em', color: 'white' }}>
RECENT MATCHES<span style={{ color: '#a855f7' }}>.</span>
</h2>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 11, color: '#1e3a5f', letterSpacing: '0.1em' }}>Last 10 games</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{recentMatches.map((m, i) => (
<div key={i} className="match-row" style={{ display: 'grid', gridTemplateColumns: '64px 1fr auto', gap: 12, alignItems: 'center', padding: '14px 16px', border: `1px solid ${m.result === 'WIN' ? 'rgba(16,185,129,0.15)' : 'rgba(239,68,68,0.15)'}`, borderLeft: `3px solid ${m.result === 'WIN' ? '#10b981' : '#ef4444'}`, borderRadius: 4, background: m.result === 'WIN' ? 'rgba(16,185,129,0.04)' : 'rgba(239,68,68,0.04)', cursor: 'default' }}>
<div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 13, fontWeight: 900, color: m.result === 'WIN' ? '#10b981' : '#ef4444' }}>{m.result}</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 8, color: '#334155', letterSpacing: '0.1em', marginTop: 2 }}>{m.mode}</div>
</div>
<div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 15, color: 'white', fontWeight: 700 }}>
{m.kills} / {m.deaths} / {m.assists}
<span style={{ fontSize: 11, color: '#475569', marginLeft: 6 }}>K/D/A</span>
</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 9, color: '#334155', marginTop: 2 }}>{m.duration}</div>
</div>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 14, fontWeight: 900, color: m.rating.startsWith('+') ? '#10b981' : '#ef4444', textAlign: 'right' }}>
{m.rating}
<div style={{ fontSize: 8, color: '#334155', letterSpacing: '0.1em' }}>RATING</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Footer */}
<footer style={{ background: '#000000', borderTop: '1px solid rgba(6,182,212,0.08)', padding: '24px 48px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontFamily: 'Orbitron, sans-serif', fontSize: 14, fontWeight: 900, color: '#1e3a5f', letterSpacing: '0.1em' }}>NEXUS ARENA</div>
<div style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 11, color: '#1e293b', letterSpacing: '0.1em' }}>
© 2024 NEXUS ARENA STUDIOS. ALL RIGHTS RESERVED.
</div>
<div style={{ display: 'flex', gap: 16 }}>
{['Twitter', 'Discord', 'YouTube', 'Twitch'].map((s) => (
<span key={s} style={{ fontFamily: 'Rajdhani, sans-serif', fontSize: 12, color: '#1e3a5f', cursor: 'pointer', letterSpacing: '0.05em' }}>{s}</span>
))}
</div>
</footer>
</div>
);
}

View File

@@ -1,612 +0,0 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
/* ══════════════════════════════════════════════
DATA
══════════════════════════════════════════════ */
const portfolio = [
{ title: '한남동 단독주택', cat: '주거 인테리어', area: '245㎡', img: 'https://picsum.photos/seed/interior-hannam/800/600' },
{ title: '청담 파인다이닝', cat: '상업 공간', area: '190㎡', img: 'https://picsum.photos/seed/interior-dining/800/600' },
{ title: '성수 브랜드 오피스', cat: '업무 공간', area: '380㎡', img: 'https://picsum.photos/seed/interior-office/800/600' },
{ title: '용산 아파트 리모델링', cat: '리모델링', area: '95㎡', img: 'https://picsum.photos/seed/interior-remodel/800/600' },
{ title: '강남 카페 에스프레소랩', cat: '상업 공간', area: '120㎡', img: 'https://picsum.photos/seed/interior-cafe/800/600' },
];
const services = [
{
title: '주거 인테리어', sub: 'Residential',
desc: '생활의 리듬에 맞춘 공간을 설계합니다. 단독주택부터 아파트까지, 당신의 일상이 더 아름다워지도록 모든 디테일을 손수 고릅니다.',
details: ['공간 기획 및 3D 시뮬레이션', '자재 선정 동행 서비스', '시공 전 과정 PM', '준공 후 AS 1년'],
img: 'https://picsum.photos/seed/interior-residential/800/600',
},
{
title: '상업 공간 디자인', sub: 'Commercial',
desc: '브랜드의 철학이 공간 언어로 번역됩니다. 첫 방문객이 문을 열었을 때 느끼는 그 감정까지 설계의 범위입니다.',
details: ['브랜드 아이덴티티 반영', '동선 및 고객 UX 설계', '조명·음향 플래닝', '설비 협력사 연계'],
img: 'https://picsum.photos/seed/interior-commercial/800/600',
},
{
title: '리모델링 & 재생', sub: 'Remodeling',
desc: '기존 공간의 가능성을 새로운 시선으로 바라봅니다. 구조적 변경부터 마감재 교체까지, 완전한 변신을 지원합니다.',
details: ['현장 실측 및 구조 분석', '철거~완공 원스톱', '예산 내 최적 시공', '친환경 자재 우선 적용'],
img: 'https://picsum.photos/seed/interior-remodeling/800/600',
},
];
const testimonials = [
{ name: '하윤서', role: '한남동 단독주택 의뢰인', u: 'hayunseo', rating: 5, highlight: '계획보다 적은 예산',
text: '처음엔 예산이 걱정됐는데, 아우라 팀이 범위를 명확히 정해줘서 오히려 계획보다 적게 들었습니다. 무엇보다 완공된 공간에서 매일 아침 커피 한 잔 하는 지금이 너무 행복해요.' },
{ name: '박도현', role: '카페 에스프레소랩 대표', u: 'parkdohyun', rating: 5, highlight: '매출 340% 상승',
text: '우리 브랜드 철학을 완벽하게 공간으로 옮겨줬습니다. 오픈 첫날부터 SNS 바이럴이 터졌고, 오픈 3개월 만에 매출이 전년 대비 340% 올랐어요.' },
{ name: '이서진', role: '루미너스 COO', u: 'leeseojin', rating: 5, highlight: '직원 만족도 93점',
text: '직원들이 출근하고 싶은 공간을 만드는 게 목표였습니다. 리모델링 후 직원 만족도 설문에서 93점, 퇴직률이 절반으로 줄었습니다.' },
];
const steps = [
{ num: '01', title: '무료 상담', desc: '공간 사진, 예산, 취향을 공유해 주세요. 72시간 내 맞춤 제안서를 드립니다.' },
{ num: '02', title: '콘셉트 기획', desc: '무드보드와 3D 시뮬레이션으로 완공 이후를 미리 경험합니다.' },
{ num: '03', title: '시공', desc: '전담 PM이 공정마다 현장을 점검하고 일일 리포트를 공유합니다.' },
{ num: '04', title: '준공 & AS', desc: '완공 후 1년간 무상 AS. 공간이 오래 아름답도록 함께합니다.' },
];
/* ══════════════════════════════════════════════
SVG ICONS
══════════════════════════════════════════════ */
const StarIcon = ({ filled }: { filled: boolean }) => (
<svg width="13" height="13" viewBox="0 0 13 13" fill={filled ? '#8B6914' : 'none'} stroke="#8B6914" strokeWidth="1">
<path d="M6.5 1l1.5 3 3.5.5-2.5 2.4.6 3.4L6.5 9 3 10.3l.6-3.4L1 4.9 4.5 4.4z" />
</svg>
);
const CheckIcon = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<circle cx="7" cy="7" r="6.5" stroke="#4E5C3E" strokeWidth="1" />
<path d="M4.5 7l2 2 3-3" stroke="#4E5C3E" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
const ArrowRight = ({ color = '#8B6914' }: { color?: string }) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 8h10M9 4l4 4-4 4" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
/* ══════════════════════════════════════════════
CONSTANTS
══════════════════════════════════════════════ */
const BANNER_H = 40;
const NAV_H = 72;
/* ══════════════════════════════════════════════
PAGE COMPONENT
══════════════════════════════════════════════ */
export default function InteriorSample() {
const [scrolled, setScrolled] = useState(false);
const [showTop, setShowTop] = useState(false);
useEffect(() => {
const scroller: HTMLElement =
(document.querySelector('.main-content') as HTMLElement | null) ??
document.documentElement;
const onNavScroll = () => {
setScrolled(scroller.scrollTop > 60);
setShowTop(scroller.scrollTop > 400);
};
scroller.addEventListener('scroll', onNavScroll, { passive: true });
const scrollToTop = () => scroller.scrollTo({ top: 0, behavior: 'smooth' });
const topBtn = document.getElementById('au-top-btn');
if (topBtn) topBtn.addEventListener('click', scrollToTop);
const observer = new IntersectionObserver(
(entries) => entries.forEach((e) => { if (e.isIntersecting) e.target.classList.add('au-visible'); }),
{ threshold: 0.08, root: scroller === document.documentElement ? null : scroller }
);
document.querySelectorAll('.au-reveal').forEach((el) => observer.observe(el));
return () => {
scroller.removeEventListener('scroll', onNavScroll);
if (topBtn) topBtn.removeEventListener('click', scrollToTop);
observer.disconnect();
};
}, []);
const GOLD = '#8B6914';
const DARK = '#1C1A17';
const SAGE = '#4E5C3E';
const CREAM = '#FAF8F5';
const SURFACE = '#F0ECE4';
return (
<div className="au-page" style={{ background: CREAM, color: DARK, fontFamily: "'Pretendard','Apple SD Gothic Neo',system-ui,sans-serif", overflowX: 'hidden' }}>
{/* ══ 폰트 + 전역 CSS ══ */}
<style dangerouslySetInnerHTML={{ __html: `
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400;1,600&display=swap');
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.min.css');
/* 전역 리셋을 .au-page 하위로 한정 — 사이드바 오염 방지 */
.au-page, .au-page *, .au-page *::before, .au-page *::after { box-sizing: border-box; margin: 0; padding: 0; }
@keyframes au-fadeUp {
from { opacity: 0; transform: translateY(2rem); filter: blur(4px); }
to { opacity: 1; transform: none; filter: blur(0); }
}
@keyframes au-float {
0%,100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes au-marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes au-pulse-gold {
0%,100% { box-shadow: 0 0 0 0 rgba(139,105,20,.3); }
50% { box-shadow: 0 0 0 10px rgba(139,105,20,0); }
}
/* 스크롤 reveal */
.au-reveal {
opacity: 0; transform: translateY(1.5rem); filter: blur(2px);
transition: opacity .7s cubic-bezier(.16,1,.3,1),
transform .7s cubic-bezier(.16,1,.3,1),
filter .7s cubic-bezier(.16,1,.3,1);
}
.au-reveal.au-visible { opacity: 1; transform: none; filter: none; }
.au-reveal:nth-child(2) { transition-delay: 80ms; }
.au-reveal:nth-child(3) { transition-delay: 160ms; }
.au-reveal:nth-child(4) { transition-delay: 240ms; }
/* 네비 */
.au-nav-link { color: #6B6456; text-decoration: none; font-size: 14px; font-weight: 500; transition: color .3s; }
.au-nav-link:hover { color: #1C1A17; }
.au-nav-link-light { color: rgba(245,237,223,.7); text-decoration: none; font-size: 14px; font-weight: 500; transition: color .3s; }
.au-nav-link-light:hover { color: #F5EDDF; }
/* 버튼 */
.au-btn-primary {
display: inline-flex; align-items: center; gap: 10px;
background: #1C1A17; color: #FAF8F5; border: none; border-radius: 100px;
padding: 14px 28px 14px 20px; font-size: 15px; font-weight: 600;
font-family: inherit; cursor: pointer; text-decoration: none;
transition: transform .4s cubic-bezier(.16,1,.3,1), box-shadow .4s cubic-bezier(.16,1,.3,1);
}
.au-btn-primary:hover { transform: scale(1.02); box-shadow: 0 12px 36px rgba(28,26,23,.25); }
.au-btn-primary:active { transform: scale(.98); }
.au-btn-ghost {
display: inline-flex; align-items: center; gap: 8px;
background: transparent; color: #1C1A17;
border: 1px solid rgba(28,26,23,.2); border-radius: 100px;
padding: 13px 24px; font-size: 14px; font-weight: 500;
font-family: inherit; cursor: pointer; text-decoration: none;
transition: border-color .3s, background .3s;
}
.au-btn-ghost:hover { border-color: rgba(28,26,23,.5); background: rgba(28,26,23,.04); }
/* 포트폴리오 */
.au-portfolio-cell { overflow: hidden; border-radius: 16px; position: relative; cursor: pointer; }
.au-portfolio-img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .7s cubic-bezier(.16,1,.3,1); }
.au-portfolio-cell:hover .au-portfolio-img { transform: scale(1.04); }
.au-portfolio-overlay {
position: absolute; inset: 0;
background: linear-gradient(to top, rgba(28,26,23,.7) 0%, transparent 55%);
opacity: 0; transition: opacity .4s cubic-bezier(.16,1,.3,1);
display: flex; flex-direction: column; justify-content: flex-end; padding: 22px;
}
.au-portfolio-cell:hover .au-portfolio-overlay { opacity: 1; }
/* 서비스 카드 */
.au-service-card {
background: white; border-radius: 20px; overflow: hidden;
box-shadow: 0 4px 32px rgba(28,26,23,.06);
transition: transform .4s cubic-bezier(.16,1,.3,1), box-shadow .4s cubic-bezier(.16,1,.3,1);
}
.au-service-card:hover { transform: translateY(-4px); box-shadow: 0 20px 60px rgba(28,26,23,.12); }
/* Double-Bezel 후기 */
.au-testimony { background: rgba(240,236,228,.5); border-radius: 24px; padding: 6px; border: 1px solid rgba(139,105,20,.12); transition: border-color .3s; }
.au-testimony:hover { border-color: rgba(139,105,20,.3); }
.au-testimony-inner { background: white; border-radius: 18px; padding: 28px 26px; box-shadow: inset 0 1px 1px rgba(255,255,255,.8); height: 100%; }
/* 프로세스 번호 */
.au-step-num { font-family:'Playfair Display',Georgia,serif; font-size:48px; font-weight:700; color:rgba(139,105,20,.15); line-height:1; margin-bottom:12px; }
/* 히어로 영상 프레임 */
.au-video-frame {
position: relative;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 40px 100px rgba(10,8,6,.45), 0 0 0 1px rgba(139,105,20,.2);
}
.au-video-frame::after {
content: '';
position: absolute; inset: 0;
border-radius: 20px;
box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
pointer-events: none;
}
.au-video-frame video {
display: block; width: 100%; height: 100%; object-fit: cover;
}
@media (max-width: 1024px) {
.au-hero-grid { grid-template-columns: 1fr !important; }
.au-hero-video-col { display: none !important; }
.au-service-grid { grid-template-columns: 1fr !important; }
.au-process-grid { grid-template-columns: 1fr 1fr !important; }
.au-portfolio-grid { grid-template-columns: 1fr 1fr !important; }
.au-portfolio-span2 { grid-column: span 1 !important; }
.au-testimony-grid { grid-template-columns: 1fr 1fr !important; }
}
@media (max-width: 640px) {
.au-process-grid { grid-template-columns: 1fr !important; }
.au-testimony-grid { grid-template-columns: 1fr !important; }
.au-portfolio-grid { grid-template-columns: 1fr !important; }
}
`}} />
{/* ══ BACK BANNER ══ */}
<div style={{
background: 'linear-gradient(135deg,#1e1b4b,#312e81)',
height: BANNER_H, display: 'flex', alignItems: 'center',
padding: '0 24px', gap: 12, flexShrink: 0,
}}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none' }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#fcd34d', fontSize: 12, fontFamily: "'Playfair Display',serif", fontStyle: 'italic', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* ══ NAV ══ */}
<nav style={{
position: 'sticky', top: 0, zIndex: 50, height: NAV_H,
display: 'flex', alignItems: 'center', padding: '0 48px',
background: scrolled ? 'rgba(250,248,245,.94)' : 'rgba(10,8,6,.55)',
backdropFilter: 'blur(20px)',
borderBottom: scrolled ? '1px solid rgba(139,105,20,.1)' : '1px solid rgba(255,255,255,.06)',
transition: 'background .45s cubic-bezier(.16,1,.3,1), border-color .45s',
}}>
<div style={{ maxWidth: 1200, margin: '0 auto', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div>
<div style={{ fontFamily: "'Playfair Display',Georgia,serif", fontSize: 20, fontWeight: 700, color: scrolled ? DARK : '#F5EDDF', letterSpacing: '-0.01em', transition: 'color .45s' }}>
Aura Interior
</div>
<div style={{ fontSize: 10, color: scrolled ? '#A0917C' : 'rgba(245,237,223,.5)', letterSpacing: '0.2em', textTransform: 'uppercase', marginTop: -2, transition: 'color .45s' }}>
</div>
</div>
<div style={{ display: 'flex', gap: 32 }}>
{['포트폴리오', '서비스', '프로세스', '고객 후기', '상담 신청'].map((l) => (
<a key={l} href={`#${l}`} className={scrolled ? 'au-nav-link' : 'au-nav-link-light'}>{l}</a>
))}
</div>
<Link href="#contact" className="au-btn-primary"
style={{ background: scrolled ? DARK : 'rgba(139,105,20,.9)', fontSize: 13, padding: '10px 20px 10px 16px', transition: 'background .45s, transform .4s, box-shadow .4s' }}>
<span style={{ width: 28, height: 28, borderRadius: '50%', background: 'rgba(250,248,245,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<ArrowRight color="#FAF8F5" />
</span>
</Link>
</div>
</nav>
{/* ══ HERO — 좌: 텍스트 / 우: 영상 ══ */}
<section style={{
background: DARK,
minHeight: `calc(100dvh - ${BANNER_H}px - ${NAV_H}px)`,
display: 'grid',
gridTemplateColumns: '1fr 1fr',
overflow: 'hidden',
}} className="au-hero-grid">
{/* 좌: 텍스트 */}
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '80px 64px 80px 80px', position: 'relative' }}>
{/* 미세 노이즈 */}
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'url("data:image/svg+xml,%3Csvg viewBox=\'0 0 200 200\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'n\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.8\' numOctaves=\'4\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23n)\'/%3E%3C/svg%3E")', opacity: 0.035, pointerEvents: 'none' }} />
<div style={{ animation: 'au-fadeUp .9s cubic-bezier(.16,1,.3,1) both', position: 'relative' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'rgba(139,105,20,.18)', border: '1px solid rgba(139,105,20,.4)', borderRadius: 100, padding: '5px 14px', marginBottom: 24 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: GOLD }} />
<span style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase' }}> </span>
</div>
<h1 style={{ fontFamily: "'Playfair Display',Georgia,serif", fontSize: 'clamp(36px,4.5vw,64px)', fontWeight: 700, lineHeight: 1.14, color: '#F5EDDF', letterSpacing: '-0.02em', marginBottom: 20, wordBreak: 'keep-all' }}>
<br /><br />
<em style={{ color: GOLD, fontStyle: 'italic' }}>.</em>
</h1>
<p style={{ fontSize: 16, color: 'rgba(245,237,223,.58)', lineHeight: 1.85, maxWidth: 400, marginBottom: 36, wordBreak: 'keep-all' }}>
12 247 .<br />
, .
</p>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center', marginBottom: 48 }}>
<Link href="#contact" className="au-btn-primary" style={{ background: GOLD, color: DARK }}>
<span style={{ width: 32, height: 32, borderRadius: '50%', background: 'rgba(28,26,23,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<ArrowRight color={DARK} />
</span>
</Link>
<a href="#포트폴리오" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'rgba(245,237,223,.6)', fontSize: 14, textDecoration: 'none', transition: 'color .3s' }}
onMouseEnter={e => (e.currentTarget.style.color = '#F5EDDF')}
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(245,237,223,.6)')}>
<ArrowRight color="rgba(245,237,223,.6)" />
</a>
</div>
{/* 미니 통계 */}
<div style={{ display: 'flex', gap: 36, paddingTop: 28, borderTop: '1px solid rgba(250,248,245,.08)' }}>
{[['247+','완공 프로젝트'],['4.96','고객 만족도'],['12년','디자인 경력']].map(([n, l]) => (
<div key={l}>
<div style={{ fontFamily: "'Playfair Display',serif", fontSize: 26, fontWeight: 700, color: '#F5EDDF' }}>{n}</div>
<div style={{ fontSize: 12, color: 'rgba(245,237,223,.4)', marginTop: 3 }}>{l}</div>
</div>
))}
</div>
</div>
</div>
{/* 우: 영상 패널 */}
<div className="au-hero-video-col" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '48px 64px 48px 32px', background: 'linear-gradient(135deg,rgba(10,8,6,0) 0%,rgba(139,105,20,.04) 100%)' }}>
{/* 배경 글로우 */}
<div style={{ position: 'absolute', width: 360, height: 500, borderRadius: '50%', background: 'radial-gradient(ellipse,rgba(139,105,20,.12) 0%,transparent 70%)', pointerEvents: 'none' }} />
{/* 영상 프레임 — 세로형 영상을 자연스럽게 표시 */}
<div style={{ position: 'relative', zIndex: 1 }}>
{/* Award 뱃지 */}
<div style={{ position: 'absolute', top: -16, right: -20, zIndex: 2, background: 'rgba(250,248,245,.97)', borderRadius: 100, padding: '7px 16px', boxShadow: '0 8px 32px rgba(10,8,6,.3)', border: '1px solid rgba(139,105,20,.15)', display: 'flex', alignItems: 'center', gap: 6 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: GOLD }} />
<span style={{ fontSize: 11, fontFamily: "'Playfair Display',serif", fontStyle: 'italic', fontWeight: 700, color: DARK, whiteSpace: 'nowrap' }}>Award Winner 2024</span>
</div>
{/* 영상 컨테이너: 세로 비율, 적당한 크기 */}
<div className="au-video-frame" style={{ width: 'clamp(240px,22vw,320px)', aspectRatio: '9/16' }}>
<video autoPlay muted loop playsInline src="/interior-hero.mp4" />
</div>
{/* 플로팅 리뷰 배지 */}
<div style={{ position: 'absolute', bottom: -20, left: -28, background: 'white', borderRadius: 14, padding: '14px 18px', boxShadow: '0 20px 60px rgba(10,8,6,.3)', animation: 'au-float 5s ease-in-out infinite', border: '1px solid rgba(139,105,20,.1)', zIndex: 2 }}>
<div style={{ display: 'flex', gap: 3, marginBottom: 5 }}>
{[1,2,3,4,5].map(i => <StarIcon key={i} filled />)}
</div>
<div style={{ fontSize: 12, fontWeight: 700, color: DARK }}> · </div>
<div style={{ fontSize: 11, color: '#A0917C', marginTop: 2 }}> 5.0 / 5.0</div>
</div>
</div>
</div>
</section>
{/* ══ PORTFOLIO BENTO ══ */}
<section id="포트폴리오" style={{ padding: '100px 80px', background: SURFACE }}>
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
<div className="au-reveal" style={{ marginBottom: 56, display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
<div>
<div style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>Portfolio</div>
<h2 style={{ fontFamily: "'Playfair Display',serif", fontSize: 'clamp(32px,3.5vw,48px)', fontWeight: 700, color: DARK, letterSpacing: '-0.02em', lineHeight: 1.2, wordBreak: 'keep-all' }}>
<br />
</h2>
</div>
<a href="#" className="au-btn-ghost" style={{ flexShrink: 0 }}> <ArrowRight /></a>
</div>
<div className="au-reveal au-portfolio-grid" style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr 1fr', gridTemplateRows: 'auto auto', gap: 14 }}>
<div className="au-portfolio-cell" style={{ gridRow: 'span 2', minHeight: 580 }}>
<img src={portfolio[0].img} alt={portfolio[0].title} className="au-portfolio-img" />
<div className="au-portfolio-overlay">
<div style={{ fontSize: 11, color: 'rgba(250,248,245,.6)', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: 4 }}>{portfolio[0].cat}</div>
<div style={{ fontSize: 18, fontWeight: 700, color: 'white', fontFamily: "'Playfair Display',serif" }}>{portfolio[0].title}</div>
<div style={{ fontSize: 12, color: 'rgba(250,248,245,.7)', marginTop: 4 }}>{portfolio[0].area}</div>
</div>
</div>
{[1, 2].map((idx) => (
<div key={idx} className="au-portfolio-cell" style={{ minHeight: 280 }}>
<img src={portfolio[idx].img} alt={portfolio[idx].title} className="au-portfolio-img" />
<div className="au-portfolio-overlay">
<div style={{ fontSize: 11, color: 'rgba(250,248,245,.6)', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: 4 }}>{portfolio[idx].cat}</div>
<div style={{ fontSize: 16, fontWeight: 700, color: 'white', fontFamily: "'Playfair Display',serif" }}>{portfolio[idx].title}</div>
</div>
</div>
))}
<div className="au-portfolio-cell au-portfolio-span2" style={{ gridColumn: 'span 2', minHeight: 280 }}>
<img src={portfolio[4].img} alt={portfolio[4].title} className="au-portfolio-img" />
<div className="au-portfolio-overlay">
<div style={{ fontSize: 11, color: 'rgba(250,248,245,.6)', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: 4 }}>{portfolio[4].cat}</div>
<div style={{ fontSize: 18, fontWeight: 700, color: 'white', fontFamily: "'Playfair Display',serif" }}>{portfolio[4].title}</div>
<div style={{ fontSize: 12, color: 'rgba(250,248,245,.7)', marginTop: 4 }}>{portfolio[4].area}</div>
</div>
</div>
</div>
</div>
</section>
{/* ══ SERVICES ZIG-ZAG ══ */}
<section id="서비스" style={{ padding: '100px 80px', background: CREAM }}>
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
<div className="au-reveal" style={{ textAlign: 'center', marginBottom: 72 }}>
<div style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>Services</div>
<h2 style={{ fontFamily: "'Playfair Display',serif", fontSize: 'clamp(32px,3.5vw,48px)', fontWeight: 700, color: DARK, letterSpacing: '-0.02em', wordBreak: 'keep-all' }}>
</h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 48 }}>
{services.map((svc, i) => (
<div key={svc.title} className="au-reveal au-service-card au-service-grid" style={{ display: 'grid', gridTemplateColumns: i % 2 === 0 ? '1fr 1.2fr' : '1.2fr 1fr', gap: 0 }}>
<div style={{ order: i % 2 === 0 ? 2 : 1, position: 'relative', minHeight: 400, overflow: 'hidden', borderRadius: i % 2 === 0 ? '0 20px 20px 0' : '20px 0 0 20px' }}>
<img src={svc.img} alt={svc.title} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform .7s cubic-bezier(.16,1,.3,1)' }} loading="lazy" />
</div>
<div style={{ order: i % 2 === 0 ? 1 : 2, padding: '52px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
<div style={{ fontSize: 11, color: SAGE, fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: 14 }}>{svc.sub}</div>
<h3 style={{ fontFamily: "'Playfair Display',serif", fontSize: 32, fontWeight: 700, color: DARK, letterSpacing: '-0.02em', marginBottom: 18, lineHeight: 1.2, wordBreak: 'keep-all' }}>{svc.title}</h3>
<p style={{ fontSize: 15, color: '#6B6456', lineHeight: 1.85, marginBottom: 28, wordBreak: 'keep-all' }}>{svc.desc}</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 36 }}>
{svc.details.map((d) => (
<div key={d} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<CheckIcon /><span style={{ fontSize: 14, color: '#5A5148' }}>{d}</span>
</div>
))}
</div>
<a href="#contact" className="au-btn-ghost" style={{ alignSelf: 'flex-start' }}>
<ArrowRight />
</a>
</div>
</div>
))}
</div>
</div>
</section>
{/* ══ PROCESS (dark) ══ */}
<section id="프로세스" style={{ padding: '100px 80px', background: DARK }}>
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
<div className="au-reveal" style={{ textAlign: 'center', marginBottom: 72 }}>
<div style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>Process</div>
<h2 style={{ fontFamily: "'Playfair Display',serif", fontSize: 'clamp(32px,3.5vw,48px)', fontWeight: 700, color: '#F5EDDF', letterSpacing: '-0.02em', wordBreak: 'keep-all' }}>
</h2>
</div>
<div className="au-process-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 2 }}>
{steps.map((s, i) => (
<div key={s.num} className="au-reveal" style={{ padding: '40px 32px', borderLeft: i > 0 ? '1px solid rgba(250,248,245,.06)' : 'none' }}>
<div className="au-step-num">{s.num}</div>
<div style={{ fontFamily: "'Playfair Display',serif", fontSize: 20, fontWeight: 600, color: '#F5EDDF', marginBottom: 12, lineHeight: 1.3 }}>{s.title}</div>
<p style={{ fontSize: 14, color: '#8A7E70', lineHeight: 1.8, wordBreak: 'keep-all' }}>{s.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* ══ TESTIMONIALS MASONRY ══ */}
<section id="고객 후기" style={{ padding: '100px 80px', background: SURFACE }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div className="au-reveal" style={{ textAlign: 'center', marginBottom: 64 }}>
<div style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>Reviews</div>
<h2 style={{ fontFamily: "'Playfair Display',serif", fontSize: 'clamp(32px,3.5vw,48px)', fontWeight: 700, color: DARK, letterSpacing: '-0.02em', wordBreak: 'keep-all' }}>
</h2>
</div>
<div className="au-testimony-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16, alignItems: 'start' }}>
{testimonials.map((t, i) => (
<div key={t.name} className="au-reveal au-testimony" style={{ marginTop: i === 1 ? 40 : 0 }}>
<div className="au-testimony-inner">
<div style={{ display: 'inline-block', background: 'rgba(139,105,20,.08)', borderRadius: 100, padding: '4px 12px', marginBottom: 18, border: '1px solid rgba(139,105,20,.15)' }}>
<span style={{ fontSize: 11, color: GOLD, fontWeight: 700 }}>{t.highlight}</span>
</div>
<div style={{ fontFamily: "'Playfair Display',serif", fontSize: 48, color: 'rgba(139,105,20,.15)', lineHeight: 0.8, marginBottom: 14 }}>"</div>
<p style={{ fontSize: 15, color: '#4A4440', lineHeight: 1.85, marginBottom: 24, wordBreak: 'keep-all' }}>{t.text}</p>
<div style={{ display: 'flex', gap: 3, marginBottom: 16 }}>
{[1,2,3,4,5].map(j => <StarIcon key={j} filled={j <= t.rating} />)}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingTop: 16, borderTop: '1px solid rgba(139,105,20,.08)' }}>
<img src={`https://i.pravatar.cc/80?u=${t.u}`} alt={t.name} style={{ width: 40, height: 40, borderRadius: '50%', objectFit: 'cover' }} loading="lazy" />
<div>
<div style={{ fontSize: 14, fontWeight: 700, color: DARK }}>{t.name}</div>
<div style={{ fontSize: 12, color: '#A0917C' }}>{t.role}</div>
</div>
</div>
</div>
</div>
))}
</div>
<div className="au-reveal" style={{ marginTop: 72, overflow: 'hidden', borderTop: '1px solid rgba(139,105,20,.1)', paddingTop: 40 }}>
<div style={{ fontSize: 11, color: '#A0917C', textAlign: 'center', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: 24 }}>함께한 브랜드들</div>
<div style={{ display: 'flex', animation: 'au-marquee 22s linear infinite', width: 'fit-content', gap: 64 }}>
{['에스프레소랩','루미너스','플로우캔버스','스텔라랩스','넥스트비전','브릿지웍스',
'에스프레소랩','루미너스','플로우캔버스','스텔라랩스','넥스트비전','브릿지웍스'].map((b, i) => (
<span key={i} style={{ fontFamily: "'Playfair Display',serif", fontSize: 18, fontWeight: 600, color: 'rgba(28,26,23,.18)', whiteSpace: 'nowrap', fontStyle: 'italic' }}>{b}</span>
))}
</div>
</div>
</div>
</section>
{/* ══ CTA ══ */}
<section id="contact" style={{ padding: '120px 80px', background: DARK, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', right: -100, top: '50%', transform: 'translateY(-50%)', width: 600, height: 600, borderRadius: '50%', background: 'radial-gradient(circle,rgba(139,105,20,.1),transparent 70%)', pointerEvents: 'none' }} />
<div style={{ maxWidth: 720, margin: '0 auto', textAlign: 'center', position: 'relative', zIndex: 1 }}>
<div className="au-reveal" style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'rgba(139,105,20,.12)', border: '1px solid rgba(139,105,20,.25)', borderRadius: 100, padding: '5px 14px', marginBottom: 32 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: GOLD }} />
<span style={{ fontSize: 11, color: GOLD, fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase' }}>72시간 내 제안서 발송</span>
</div>
<h2 className="au-reveal" style={{ fontFamily: "'Playfair Display',serif", fontSize: 'clamp(36px,5vw,56px)', fontWeight: 700, color: '#F5EDDF', letterSpacing: '-0.02em', lineHeight: 1.2, marginBottom: 20, wordBreak: 'keep-all' }}>
당신의 공간 이야기를<br />
<em style={{ color: GOLD }}>지금 시작하세요.</em>
</h2>
<p className="au-reveal" style={{ fontSize: 16, color: '#8A7E70', lineHeight: 1.85, marginBottom: 44, wordBreak: 'keep-all' }}>
사진 한 장과 예산만 알려주세요.<br />
72시간 내에 맞춤 제안서와 무드보드를 드립니다. 무료입니다.
</p>
<div className="au-reveal" style={{ display: 'flex', gap: 14, justifyContent: 'center', flexWrap: 'wrap' }}>
<Link href="/freelance?service=website" className="au-btn-primary" style={{ background: GOLD, color: DARK, fontSize: 16, padding: '16px 36px 16px 28px', animation: 'au-pulse-gold 3s infinite' }}>
<span style={{ width: 34, height: 34, borderRadius: '50%', background: 'rgba(28,26,23,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<ArrowRight color={DARK} />
</span>
무료 상담 신청하기
</Link>
<a href="tel:02-1234-5678" className="au-btn-ghost" style={{ color: '#F5EDDF', borderColor: 'rgba(245,237,223,.15)' }}>
02-1234-5678
</a>
</div>
<div className="au-reveal" style={{ display: 'flex', gap: 24, justifyContent: 'center', marginTop: 40 }}>
{['완공 보장','계약서 필수','AS 1년'].map((b) => (
<div key={b} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<CheckIcon /><span style={{ fontSize: 13, color: '#8A7E70' }}>{b}</span>
</div>
))}
</div>
</div>
</section>
{/* ══ FOOTER ══ */}
<footer style={{ background: '#100E0B', padding: '40px 80px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
<div>
<div style={{ fontFamily: "'Playfair Display',serif", fontSize: 16, fontWeight: 700, color: '#F5EDDF', fontStyle: 'italic' }}>Aura Interior</div>
<div style={{ fontSize: 12, color: '#4A4035', marginTop: 4 }}>© 2024 아우라 인테리어. All rights reserved.</div>
</div>
<div style={{ display: 'flex', gap: 24 }}>
{['포트폴리오','서비스 안내','상담 신청','Instagram'].map((l) => (
<a key={l} href="#" style={{ fontSize: 13, color: '#4A4035', textDecoration: 'none', transition: 'color .3s' }}
onMouseEnter={e => (e.currentTarget.style.color = '#A0917C')}
onMouseLeave={e => (e.currentTarget.style.color = '#4A4035')}>{l}</a>
))}
</div>
</footer>
{/* ══ SCROLL TO TOP ══ */}
<button
id="au-top-btn"
style={{
position: 'fixed', bottom: '5.5rem', right: '2rem', zIndex: 200,
width: 48, height: 48, borderRadius: '50%',
background: GOLD, border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 8px 32px rgba(139,105,20,0.35)',
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="#FAF8F5" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="18 15 12 9 6 15"/>
</svg>
</button>
</div>
);
}

View File

@@ -1,408 +0,0 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
const awards = [
{ name: 'Awwwards SOTD', count: '× 2', color: '#f59e0b' },
{ name: 'CSS Design Awards', count: 'Winner', color: '#06b6d4' },
{ name: 'Adobe Design Award', count: '은상', color: '#ec4899' },
{ name: 'Google UX Cert.', count: '2023', color: '#34d399' },
];
const services = [
{ title: 'UI/UX Design', desc: '사용자 중심의 인터페이스 설계와 프로토타입', icon: 'M', color: '#00ff88' },
{ title: 'Creative Development', desc: 'Three.js · WebGL · 몰입형 웹 경험 구현', icon: 'C', color: '#a855f7' },
{ title: 'Brand Identity', desc: '로고부터 브랜드 시스템 전체 디자인', icon: 'B', color: '#06b6d4' },
{ title: 'Motion Design', desc: 'After Effects · Lottie 모션 에셋 제작', icon: 'M', color: '#f59e0b' },
];
const projects = [
{ title: 'NEON CITY UI', desc: '사이버펑크 게임 인터페이스 디자인. 넥슨 신작 게임의 전체 UI 시스템 구축', gradient: 'linear-gradient(135deg, #00ff88, #00d4ff)', tag: 'UI/UX', year: '2024', featured: true },
{ title: 'FLOW BRAND', desc: '핀테크 스타트업 브랜딩 — 시리즈 B 투자유치 직전 리브랜딩', gradient: 'linear-gradient(135deg, #a855f7, #ec4899)', tag: 'Branding', year: '2024', featured: false },
{ title: 'ORBITAL APP', desc: '위성 추적 실시간 대시보드 — NASA 오픈 API 활용', gradient: 'linear-gradient(135deg, #3b82f6, #06b6d4)', tag: 'Web App', year: '2023', featured: false },
{ title: 'TERRA SHOP', desc: '친환경 D2C 쇼핑몰 — 전환율 340% 개선 달성', gradient: 'linear-gradient(135deg, #22c55e, #84cc16)', tag: 'E-commerce', year: '2023', featured: false },
{ title: 'PULSE MOTION', desc: '글로벌 광고 에이전시를 위한 모션 그래픽 패키지', gradient: 'linear-gradient(135deg, #f59e0b, #ef4444)', tag: 'Motion', year: '2023', featured: false },
{ title: 'AXIS SYSTEM', desc: '스타트업 물류 관리 SaaS — 현재 MAU 12만', gradient: 'linear-gradient(135deg, #64748b, #475569)', tag: 'Dashboard', year: '2022', featured: false },
];
const skills = [
{ name: 'Figma', level: 98, category: 'Design' },
{ name: 'Framer', level: 92, category: 'Design' },
{ name: 'After Effects', level: 88, category: 'Motion' },
{ name: 'React', level: 90, category: 'Code' },
{ name: 'TypeScript', level: 85, category: 'Code' },
{ name: 'Three.js', level: 75, category: 'Code' },
];
const timeline = [
{ year: '2023', event: 'Google UX Design Certificate 취득', type: 'award' },
{ year: '2022', event: 'Awwwards SOTD 2회 수상', type: 'award' },
{ year: '2021', event: 'LINE Corp. UI 디자이너 입사', type: 'career' },
{ year: '2020', event: 'Hongik University 시각디자인과 졸업', type: 'edu' },
{ year: '2019', event: 'Adobe Design Award Korea 은상', type: 'award' },
];
const testimonials = [
{ client: 'Joon Park', role: 'CEO, FlowTech', text: 'Jisu가 리디자인한 결과물로 투자 피칭에서 VC들의 반응이 완전히 달라졌습니다. 결과적으로 시리즈 B 클로징에 큰 역할을 했어요.' },
{ client: 'Yuna Kim', role: 'Product Lead, Orbital', text: '기술적인 복잡함을 이렇게 아름다운 인터페이스로 풀어낼 수 있다는 게 놀라웠습니다. 유저 피드백 NPS가 34점 올랐습니다.' },
];
export default function PortfolioSample() {
const [hoveredProject, setHoveredProject] = useState<number | null>(null);
const [activeCategory, setActiveCategory] = useState<string>('All');
const categories = ['All', 'UI/UX', 'Branding', 'Web App', 'Motion'];
const filteredProjects = activeCategory === 'All' ? projects : projects.filter(p => p.tag === activeCategory);
return (
<div style={{ background: '#000000', minHeight: '100vh', color: 'white' }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
@keyframes scanline { 0% { top: -10%; } 100% { top: 110%; } }
@keyframes glow { 0%, 100% { text-shadow: 0 0 20px rgba(0,255,136,0.5); } 50% { text-shadow: 0 0 40px rgba(0,255,136,0.9), 0 0 80px rgba(0,255,136,0.3); } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideBar { from { width: 0; } to { width: 100%; } }
@keyframes marquee { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }
@keyframes pulse-green { 0%, 100% { box-shadow: 0 0 0 0 rgba(0,255,136,0.4); } 50% { box-shadow: 0 0 0 6px rgba(0,255,136,0); } }
.proj-card { transition: border-color 0.3s, transform 0.3s; }
.proj-card:hover { border-color: rgba(0,255,136,0.3) !important; transform: translateY(-4px); }
.service-card { transition: background 0.3s, border-color 0.3s; }
.service-card:hover { background: rgba(255,255,255,0.04) !important; }
.cat-btn { transition: background 0.2s, color 0.2s; cursor: pointer; }
.cat-btn:hover { color: white !important; }
.award-badge { transition: transform 0.2s; }
.award-badge:hover { transform: translateY(-2px); }
`}</style>
{/* Back Banner */}
<div style={{ background: 'linear-gradient(135deg, #1e1b4b, #312e81)', padding: '10px 24px', display: 'flex', alignItems: 'center', gap: 12 }}>
<Link href="/services/website" style={{ color: '#a5b4fc', fontSize: 13, textDecoration: 'none', fontFamily: 'Space Grotesk, sans-serif' }}>
</Link>
<span style={{ color: '#4c1d95' }}>|</span>
<span style={{ color: '#00ff88', fontSize: 12, fontFamily: 'Space Mono, monospace', fontWeight: 700 }}>
SAMPLE ·
</span>
</div>
{/* Awards Marquee */}
<div style={{ background: '#050505', borderBottom: '1px solid rgba(0,255,136,0.12)', padding: '10px 0', overflow: 'hidden' }}>
<div style={{ display: 'flex', gap: 48, animation: 'marquee 18s linear infinite', width: 'fit-content' }}>
{[...awards, ...awards].map((a, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 1l1.3 2.6L10 4.1 8 6.1l.5 2.9L6 7.6 3.5 9l.5-2.9-2-2 2.7-.5z" fill={a.color} /></svg>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: a.color, fontWeight: 700, letterSpacing: '0.1em', whiteSpace: 'nowrap' }}>{a.name}</span>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: '#374151' }}>{a.count}</span>
</div>
))}
</div>
</div>
{/* Navbar */}
<nav style={{
position: 'sticky', top: 0, zIndex: 100,
background: 'rgba(0,0,0,0.92)', backdropFilter: 'blur(20px)',
borderBottom: '1px solid rgba(0,255,136,0.1)',
padding: '0 48px', height: 64,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 15, fontWeight: 700, color: '#00ff88', letterSpacing: '-0.02em', animation: 'glow 3s ease-in-out infinite' }}>
KJ<span style={{ color: 'rgba(0,255,136,0.35)' }}>_</span>
</div>
<div style={{ display: 'flex', gap: 32 }}>
{['About', 'Work', 'Skills', 'Contact'].map((item) => (
<span key={item} style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, fontWeight: 600, color: '#374151', cursor: 'pointer', letterSpacing: '0.05em', textTransform: 'uppercase' }}>{item}</span>
))}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginRight: 8 }}>
<div style={{ width: 7, height: 7, borderRadius: '50%', background: '#00ff88', animation: 'pulse-green 2s infinite' }} />
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#00ff88' }}>Available for work</span>
</div>
<button style={{
background: 'transparent', border: '1px solid #00ff88', color: '#00ff88',
padding: '8px 20px', borderRadius: 4, fontSize: 12, fontWeight: 700,
cursor: 'pointer', fontFamily: 'Space Mono, monospace', letterSpacing: '0.08em',
}}>
HIRE ME
</button>
</div>
</nav>
{/* Hero */}
<section style={{ padding: '100px 48px 80px', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, transparent, rgba(0,255,136,0.4), transparent)', animation: 'scanline 8s linear infinite', pointerEvents: 'none' }} />
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(0,255,136,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,136,0.04) 1px, transparent 1px)', backgroundSize: '40px 40px' }} />
<div style={{ maxWidth: 1000, display: 'grid', gridTemplateColumns: '1fr auto', gap: 48, alignItems: 'center', position: 'relative', animation: 'fadeUp 0.8s ease forwards' }}>
<div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: 'rgba(0,255,136,0.6)', letterSpacing: '0.2em', marginBottom: 20, textTransform: 'uppercase' }}>
{'> Hello, World. I am'}
</div>
<h1 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 'clamp(52px, 7vw, 88px)', fontWeight: 700, lineHeight: 1.0, marginBottom: 16, letterSpacing: '-0.03em' }}>
Kim<br />
<span style={{ color: '#00ff88' }}>Jisu</span>
<span style={{ display: 'inline-block', width: 6, height: 'clamp(52px, 7vw, 88px)', background: '#00ff88', marginLeft: 8, verticalAlign: 'middle', animation: 'blink 1.2s step-end infinite' }} />
</h1>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 20, color: '#4b5563', fontWeight: 500, marginBottom: 20 }}>
Product Designer & Creative Developer
</div>
<p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 16, color: '#6b7280', lineHeight: 1.8, maxWidth: 520, marginBottom: 36 }}>
. .
</p>
<div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', alignItems: 'center' }}>
<button style={{
background: '#00ff88', color: '#000000', border: 'none', padding: '14px 32px',
fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'Space Mono, monospace',
letterSpacing: '0.05em', clipPath: 'polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px))',
}}>
VIEW WORK
</button>
<button style={{
background: 'transparent', color: '#9ca3af', border: '1px solid #1f2937',
padding: '14px 32px', fontSize: 14, fontWeight: 600, cursor: 'pointer',
fontFamily: 'Space Grotesk, sans-serif', borderRadius: 4,
}}>
Download CV
</button>
</div>
</div>
{/* Stats panel */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{[
{ val: '6+', label: 'YEARS EXP.', color: '#00ff88' },
{ val: '30+', label: 'PROJECTS', color: '#a855f7' },
{ val: '2×', label: 'AWWWARDS', color: '#f59e0b' },
{ val: '340%', label: 'MAX CVR LIFT', color: '#06b6d4' },
].map((s) => (
<div key={s.label} style={{ padding: '14px 18px', border: '1px solid #111827', borderLeft: `3px solid ${s.color}`, borderRadius: 4, background: '#050505', minWidth: 140 }}>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 24, fontWeight: 700, color: s.color }}>{s.val}</div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 9, color: '#374151', letterSpacing: '0.15em', marginTop: 2 }}>{s.label}</div>
</div>
))}
</div>
</div>
</section>
{/* Services */}
<section style={{ padding: '72px 48px', background: '#050505', borderTop: '1px solid rgba(0,255,136,0.08)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ marginBottom: 40 }}>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 10 }}>// WHAT I DO</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 32, fontWeight: 700, letterSpacing: '-0.02em' }}>
Services<span style={{ color: '#00ff88' }}>.</span>
</h2>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}>
{services.map((svc) => (
<div key={svc.title} className="service-card" style={{
padding: '24px 20px', border: '1px solid #111827', borderRadius: 8,
background: 'transparent', cursor: 'pointer', position: 'relative', overflow: 'hidden',
}}>
<div style={{ position: 'absolute', top: 0, left: 0, width: 3, height: '100%', background: svc.color, opacity: 0.6 }} />
<div style={{ width: 36, height: 36, borderRadius: 8, background: svc.color + '15', border: `1px solid ${svc.color}30`, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, fontWeight: 700, color: svc.color }}>{svc.icon}</span>
</div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 15, fontWeight: 700, color: 'white', marginBottom: 8 }}>{svc.title}</div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, color: '#4b5563', lineHeight: 1.6 }}>{svc.desc}</div>
</div>
))}
</div>
</div>
</section>
{/* Awards Detail */}
<section style={{ padding: '60px 48px', background: '#000000', borderTop: '1px solid rgba(0,255,136,0.08)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{awards.map((a) => (
<div key={a.name} className="award-badge" style={{
padding: '12px 20px', border: `1px solid ${a.color}30`, borderRadius: 8,
background: a.color + '08', display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
}}>
<svg width="14" height="14" viewBox="0 0 14 14"><path d="M7 1l1.5 3 3.5.5-2.5 2.4.6 3.4L7 8.8 3.9 10.3l.6-3.4L2 4.5 5.5 4z" fill={a.color} /></svg>
<div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, fontWeight: 700, color: 'white' }}>{a.name}</div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: a.color }}>{a.count}</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Projects */}
<section style={{ padding: '80px 48px', background: '#050505', borderTop: '1px solid rgba(0,255,136,0.08)' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 32 }}>
<div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 8 }}>// SELECTED WORK</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 36, fontWeight: 700, letterSpacing: '-0.02em' }}>
Projects<span style={{ color: '#00ff88' }}>.</span>
</h2>
</div>
<span style={{ color: '#374151', fontSize: 12, fontFamily: 'Space Mono, monospace' }}>2019 2024</span>
</div>
{/* Category filter */}
<div style={{ display: 'flex', gap: 8, marginBottom: 32 }}>
{categories.map((cat) => (
<button key={cat} className="cat-btn" onClick={() => setActiveCategory(cat)} style={{
background: activeCategory === cat ? '#00ff88' : 'transparent',
color: activeCategory === cat ? '#000' : '#374151',
border: '1px solid ' + (activeCategory === cat ? '#00ff88' : '#1f2937'),
padding: '6px 16px', borderRadius: 4, fontSize: 11,
fontWeight: 700, fontFamily: 'Space Mono, monospace', letterSpacing: '0.06em',
}}>{cat}</button>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 16 }}>
{filteredProjects.map((proj, i) => (
<div
key={proj.title}
className="proj-card"
onMouseEnter={() => setHoveredProject(i)}
onMouseLeave={() => setHoveredProject(null)}
style={{ border: '1px solid #111827', borderRadius: 12, overflow: 'hidden', cursor: 'pointer', background: '#0a0a0a', position: 'relative' }}
>
<div style={{ height: 200, background: proj.gradient, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 22, fontWeight: 700, color: 'rgba(0,0,0,0.35)', letterSpacing: '-0.02em' }}>{proj.title}</div>
</div>
{/* Hover overlay */}
<div style={{
position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(4px)',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12,
transition: 'opacity 0.3s', opacity: hoveredProject === i ? 1 : 0,
}}>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, color: 'white', fontWeight: 600, textAlign: 'center', padding: '0 20px', lineHeight: 1.5 }}>{proj.desc}</div>
<button style={{ background: '#00ff88', color: '#000', border: 'none', padding: '8px 20px', borderRadius: 4, fontSize: 11, fontWeight: 700, cursor: 'pointer', fontFamily: 'Space Mono, monospace', letterSpacing: '0.06em' }}>
VIEW PROJECT
</button>
</div>
<div style={{ position: 'absolute', top: 12, left: 12, background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 4, padding: '3px 10px', fontSize: 10, fontWeight: 700, color: 'white', fontFamily: 'Space Mono, monospace', letterSpacing: '0.1em' }}>
{proj.tag}
</div>
<div style={{ position: 'absolute', top: 12, right: 12, fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(255,255,255,0.4)' }}>
{proj.year}
</div>
</div>
<div style={{ padding: '16px 18px' }}>
<div style={{ fontSize: 15, fontWeight: 700, color: 'white', fontFamily: 'Space Grotesk, sans-serif', marginBottom: 4 }}>{proj.title}</div>
<div style={{ fontSize: 13, color: '#4b5563', fontFamily: 'Space Grotesk, sans-serif', lineHeight: 1.5 }}>{proj.desc}</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Skills + Timeline */}
<section style={{ padding: '80px 48px', background: '#000000', borderTop: '1px solid rgba(0,255,136,0.08)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 64, alignItems: 'start' }}>
<div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 10 }}>// EXPERTISE</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 32, fontWeight: 700, marginBottom: 36, letterSpacing: '-0.02em' }}>
Skills<span style={{ color: '#00ff88' }}>.</span>
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
{skills.map((s) => (
<div key={s.name}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 7 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: '#9ca3af', letterSpacing: '0.05em' }}>{s.name}</span>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 9, color: '#1f2937', letterSpacing: '0.1em', border: '1px solid #1f2937', padding: '1px 6px', borderRadius: 2 }}>{s.category}</span>
</div>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#00ff88' }}>{s.level}%</span>
</div>
<div style={{ height: 3, background: '#111827', borderRadius: 2, overflow: 'hidden' }}>
<div style={{ height: '100%', borderRadius: 2, background: 'linear-gradient(90deg, #00ff88, #00d4ff)', width: `${s.level}%`, animation: 'slideBar 1.5s ease forwards' }} />
</div>
</div>
))}
</div>
</div>
<div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 10 }}>// JOURNEY</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 32, fontWeight: 700, marginBottom: 36, letterSpacing: '-0.02em' }}>
Timeline<span style={{ color: '#00ff88' }}>.</span>
</h2>
<div style={{ position: 'relative', paddingLeft: 20 }}>
<div style={{ position: 'absolute', left: 4, top: 8, bottom: 8, width: 1, background: '#1f2937' }} />
{timeline.map((t, i) => (
<div key={i} style={{ position: 'relative', paddingBottom: 24 }}>
<div style={{
position: 'absolute', left: -20, top: 4, width: 8, height: 8, borderRadius: '50%',
background: t.type === 'award' ? '#00ff88' : t.type === 'career' ? '#3b82f6' : '#a855f7',
boxShadow: `0 0 8px ${t.type === 'award' ? 'rgba(0,255,136,0.6)' : t.type === 'career' ? 'rgba(59,130,246,0.6)' : 'rgba(168,85,247,0.6)'}`,
}} />
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#374151', marginBottom: 3 }}>{t.year}</div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, color: '#d1d5db', lineHeight: 1.5 }}>{t.event}</div>
{t.type === 'award' && (
<div style={{ marginTop: 4 }}>
<span style={{ fontFamily: 'Space Mono, monospace', fontSize: 9, color: '#00ff88', border: '1px solid rgba(0,255,136,0.3)', padding: '1px 6px', borderRadius: 2 }}>AWARD</span>
</div>
)}
</div>
))}
</div>
</div>
</div>
</section>
{/* Testimonials */}
<section style={{ padding: '72px 48px', background: '#050505', borderTop: '1px solid rgba(0,255,136,0.08)' }}>
<div style={{ maxWidth: 1000, margin: '0 auto' }}>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 10 }}>// CLIENT VOICES</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 32, fontWeight: 700, marginBottom: 36, letterSpacing: '-0.02em' }}>
Testimonials<span style={{ color: '#00ff88' }}>.</span>
</h2>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
{testimonials.map((t) => (
<div key={t.client} style={{ padding: '28px 24px', border: '1px solid #111827', borderRadius: 8, background: '#0a0a0a', position: 'relative' }}>
<svg width="28" height="20" viewBox="0 0 28 20" fill="none" style={{ marginBottom: 16, opacity: 0.25 }}>
<path d="M0 20V12C0 5.3 4 1.5 12 0l1.7 3C9.5 4.3 7.3 6.7 7 10h5.3V20H0zm14.7 0V12C14.7 5.3 18.7 1.5 26.7 0l1.3 3c-4.2 1.3-6.4 3.7-6.7 7H27V20H14.7z" fill="#00ff88" />
</svg>
<p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, color: '#9ca3af', lineHeight: 1.8, marginBottom: 20 }}>{t.text}</p>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'linear-gradient(135deg, #00ff88, #00d4ff)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13, fontWeight: 700, color: '#000', fontFamily: 'Space Mono, monospace' }}>
{t.client[0]}
</div>
<div>
<div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, fontWeight: 600, color: 'white' }}>{t.client}</div>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: '#374151' }}>{t.role}</div>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* Contact */}
<section style={{ padding: '96px 48px', textAlign: 'center', background: 'linear-gradient(180deg, #000000, #001a00)', borderTop: '1px solid rgba(0,255,136,0.1)' }}>
<div style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: 'rgba(0,255,136,0.5)', letterSpacing: '0.2em', marginBottom: 20 }}>
{'> LET\'S COLLABORATE'}
</div>
<h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 'clamp(36px, 5vw, 60px)', fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 16, animation: 'glow 3s ease-in-out infinite' }}>
Have a project<span style={{ color: '#00ff88' }}>?</span>
</h2>
<p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 16, color: '#4b5563', marginBottom: 8 }}>
jisu.kim@design.studio
</p>
<p style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: '#1f2937', marginBottom: 36 }}>
@jisu_creates · Response within 24h
</p>
<button style={{
background: '#00ff88', color: '#000000', border: 'none', padding: '16px 48px',
fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'Space Mono, monospace',
letterSpacing: '0.08em', clipPath: 'polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px))',
}}>
START A PROJECT
</button>
</section>
</div>
);
}

View File

@@ -1,663 +0,0 @@
'use client';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
/* ═══════════════════════════════════════
SVG ICONS
═══════════════════════════════════════ */
const BookOpenIcon = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
);
const StarIcon = ({ filled = true }: { filled?: boolean }) => (
<svg width="12" height="12" viewBox="0 0 24 24" fill={filled ? '#D4A853' : 'none'} stroke="#D4A853" strokeWidth="1.5">
<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"/>
</svg>
);
const QuoteIcon = () => (
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M8 8C4 10.5 2 14 2 18c0 3.3 2 5 4 5s3.5-1.5 3.5-4c0-2.3-1.2-3.5-2.5-3.5C5.5 15.5 5 15 6 13c.8-1.5 2.5-3 4-3.5L8 8zm16 0c-4 2.5-6 6-6 10 0 3.3 2 5 4 5s3.5-1.5 3.5-4c0-2.3-1.2-3.5-2.5-3.5C21.5 15.5 21 15 22 13c.8-1.5 2.5-3 4-3.5L24 8z" fill="#D4A853" opacity="0.6"/>
</svg>
);
const CheckIcon = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D4A853" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12"/>
</svg>
);
const ArrowUpRight = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7"/><polyline points="7 7 17 7 17 17"/>
</svg>
);
const ChevronDown = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9"/>
</svg>
);
/* ═══════════════════════════════════════
COUNTUP HOOK
═══════════════════════════════════════ */
function useCountUp(target: number, duration = 1600) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) return;
obs.disconnect();
const start = performance.now();
const tick = (now: number) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
setCount(Math.round(target * ease));
if (progress < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
},
{ threshold: 0.4 }
);
obs.observe(el);
return () => obs.disconnect();
}, [target, duration]);
return { count, ref };
}
/* ═══════════════════════════════════════
STAT ITEM
═══════════════════════════════════════ */
function StatItem({ value, suffix, label }: { value: number; suffix?: string; label: string }) {
const { count, ref } = useCountUp(value);
return (
<div style={{ textAlign: 'center', padding: '0 2rem' }}>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'center', gap: '2px' }}>
<span ref={ref} style={{ fontSize: '3rem', fontFamily: "'Cormorant Garamond', serif", fontWeight: 700, color: '#D4A853', lineHeight: 1 }}>{count}</span>
{suffix && <span style={{ fontSize: '1.5rem', fontFamily: "'Cormorant Garamond', serif", color: '#D4A853' }}>{suffix}</span>}
</div>
<p style={{ fontSize: '0.78rem', color: '#8A8070', marginTop: '0.5rem', letterSpacing: '0.1em', textTransform: 'uppercase', fontFamily: "'Pretendard', sans-serif" }}>{label}</p>
</div>
);
}
/* ═══════════════════════════════════════
BOOK CARD
═══════════════════════════════════════ */
interface Book {
seed: string; title: string; author: string; rating: number; genre: string; year: number; note?: string;
}
const books: Book[] = [
{ seed: 'book1', title: '파친코', author: '이민진', rating: 5, genre: '소설', year: 2024, note: '역사 속에 묻힌 삶의 무게를 느꼈다' },
{ seed: 'book2', title: '어린 왕자', author: '생텍쥐페리', rating: 5, genre: '고전', year: 2023 },
{ seed: 'book3', title: '원씽', author: '게리 켈러', rating: 4, genre: '자기계발', year: 2024 },
{ seed: 'book4', title: '채식주의자', author: '한강', rating: 5, genre: '소설', year: 2023, note: '불편함 속의 아름다움' },
{ seed: 'book5', title: '지능의 본질', author: '제프 호킨스', rating: 4, genre: '과학', year: 2024 },
{ seed: 'book6', title: '82년생 김지영', author: '조남주', rating: 4, genre: '소설', year: 2022 },
{ seed: 'book7', title: '노인과 바다', author: '헤밍웨이', rating: 5, genre: '고전', year: 2022 },
{ seed: 'book8', title: '생각에 관한 생각', author: '다니엘 카너먼', rating: 4, genre: '심리학', year: 2023 },
];
const genreColors: Record<string, string> = {
'소설': '#6B5B95', '고전': '#D4A853', '자기계발': '#4A7C59', '과학': '#2E6EA6', '심리학': '#8B4E62'
};
function BookCard({ book, large = false }: { book: Book; large?: boolean }) {
return (
<div
className="rd-reveal"
style={{
background: 'rgba(255,255,255,0.03)',
border: '1px solid rgba(212,168,83,0.15)',
borderRadius: '12px',
overflow: 'hidden',
position: 'relative',
height: large ? '420px' : '340px',
display: 'flex',
flexDirection: 'column',
transition: 'transform 0.5s cubic-bezier(0.16,1,0.3,1), box-shadow 0.5s cubic-bezier(0.16,1,0.3,1)',
cursor: 'default',
}}
onMouseEnter={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(-6px)';
(e.currentTarget as HTMLElement).style.boxShadow = '0 20px 60px rgba(212,168,83,0.12)';
}}
onMouseLeave={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
(e.currentTarget as HTMLElement).style.boxShadow = 'none';
}}
>
{/* cover */}
<div style={{ flex: 1, overflow: 'hidden', position: 'relative' }}>
<img
src={`https://picsum.photos/seed/${book.seed}/300/450`}
alt={book.title}
style={{ width: '100%', height: '100%', objectFit: 'cover', filter: 'brightness(0.75) saturate(0.7)' }}
/>
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, transparent 40%, rgba(12,11,9,0.95))' }}/>
{/* genre badge */}
<div style={{
position: 'absolute', top: '0.75rem', left: '0.75rem',
background: genreColors[book.genre] || '#6B5B95',
color: 'white', fontSize: '0.65rem', fontFamily: "'Pretendard', sans-serif",
padding: '3px 8px', borderRadius: '4px', fontWeight: 600, letterSpacing: '0.05em'
}}>{book.genre}</div>
{/* rating */}
<div style={{ position: 'absolute', top: '0.75rem', right: '0.75rem', display: 'flex', gap: '2px' }}>
{[1,2,3,4,5].map(i => <StarIcon key={i} filled={i <= book.rating}/>)}
</div>
</div>
{/* info */}
<div style={{ padding: '0.875rem', background: 'rgba(12,11,9,0.95)' }}>
<p style={{ fontSize: '0.65rem', color: '#8A8070', fontFamily: "'Pretendard', sans-serif", marginBottom: '0.25rem' }}>{book.author} · {book.year}</p>
<h3 style={{ fontSize: large ? '1.1rem' : '0.95rem', fontFamily: "'Cormorant Garamond', serif", fontWeight: 700, color: '#F0EAD8', margin: 0, wordBreak: 'keep-all', lineHeight: 1.3 }}>{book.title}</h3>
{book.note && <p style={{ fontSize: '0.72rem', color: '#6A6050', fontFamily: "'Pretendard', sans-serif", marginTop: '0.4rem', wordBreak: 'keep-all', fontStyle: 'italic' }}>"{book.note}"</p>}
</div>
</div>
);
}
/* ═══════════════════════════════════════
MAIN PAGE
═══════════════════════════════════════ */
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, root: scroller === document.documentElement ? null : scroller }
);
document.querySelectorAll('.rd-reveal').forEach(el => obs.observe(el));
return () => {
scroller.removeEventListener('scroll', onScroll);
obs.disconnect();
};
}, []);
const genres = ['전체', '소설', '고전', '자기계발', '과학', '심리학'];
const filteredBooks = activeGenre === '전체' ? books : books.filter(b => b.genre === activeGenre);
const quotes = [
{ text: '당신이 무엇을 읽느냐가 당신이 누구인지를 말해준다. 책은 당신이 선택한 삶의 궤적이다.', author: '파친코 — 이민진', genre: '소설' },
{ text: '어른들은 숫자를 좋아한다. 새 친구 이야기를 해줄 때, 중요한 것을 절대 묻지 않는다. 그의 목소리가 어떤지, 어떤 놀이를 좋아하는지.', author: '어린 왕자 — 생텍쥐페리', genre: '고전' },
{ text: '성공한 사람들은 하나에 집중하고 나머지를 내려놓는 법을 배웠다. 한 가지를 잘하면 나머지가 따라온다.', author: '원씽 — 게리 켈러', genre: '자기계발' },
{ text: '빠른 생각은 직관이고 느린 생각은 이성이다. 우리는 대부분 빠른 생각이 옳다고 착각하며 살아간다.', author: '생각에 관한 생각 — 다니엘 카너먼', genre: '심리학' },
];
const currentlyReading = {
seed: 'current1', title: '도둑맞은 집중력', author: '요한 하리', genre: '자기계발',
progress: 67, totalPages: 380, currentPage: 255,
startDate: '2024.03.10', note: '현대 사회가 어떻게 우리의 집중력을 앗아가는지 설득력 있게 풀어냄'
};
const tbr = [
{ seed: 'tbr1', title: '총, 균, 쇠', author: '재레드 다이아몬드', genre: '역사', priority: '높음' },
{ seed: 'tbr2', title: '코스모스', author: '칼 세이건', genre: '과학', priority: '높음' },
{ seed: 'tbr3', title: '사피엔스', author: '유발 하라리', genre: '역사', priority: '중간' },
];
const monthlyData = [
{ month: '10월', books: 2 }, { month: '11월', books: 3 }, { month: '12월', books: 1 },
{ month: '1월', books: 4 }, { month: '2월', books: 3 }, { month: '3월', books: 2 },
];
const maxBooks = Math.max(...monthlyData.map(d => d.books));
return (
<>
<style dangerouslySetInnerHTML={{ __html: `
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&display=swap');
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.min.css');
.rd-root { font-family: 'Pretendard', sans-serif; background: #0C0B09; color: #F0EAD8; min-height: 100dvh; }
.rd-serif { font-family: 'Cormorant Garamond', serif; }
/* grain overlay */
.rd-grain::before {
content: '';
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.07'/%3E%3C/svg%3E");
opacity: 0.4;
}
/* scroll reveal */
.rd-reveal {
opacity: 0; transform: translateY(2rem); filter: blur(2px);
transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1), transform 0.7s cubic-bezier(0.16,1,0.3,1), filter 0.7s cubic-bezier(0.16,1,0.3,1);
}
.rd-reveal.rd-visible { opacity: 1; transform: translateY(0); filter: blur(0); }
.rd-reveal:nth-child(2) { transition-delay: 0.08s; }
.rd-reveal:nth-child(3) { transition-delay: 0.16s; }
.rd-reveal:nth-child(4) { transition-delay: 0.24s; }
.rd-reveal:nth-child(5) { transition-delay: 0.32s; }
/* nav */
.rd-nav {
position: fixed; top: 1rem; left: 50%; transform: translateX(-50%); z-index: 100;
background: rgba(12,11,9,0.7); backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(212,168,83,0.2); border-radius: 100px;
padding: 0.75rem 2rem; display: flex; align-items: center; gap: 2.5rem;
white-space: nowrap;
}
/* double-bezel cards */
.rd-bezel {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(212,168,83,0.2);
border-radius: 16px; padding: 1px;
box-shadow: inset 0 1px 0 rgba(212,168,83,0.1), 0 0 0 1px rgba(0,0,0,0.5);
}
.rd-bezel-inner {
background: rgba(20,18,14,0.9);
border: 1px solid rgba(255,255,255,0.04);
border-radius: 15px; padding: 1.75rem;
}
/* genre filter */
.rd-genre-btn {
background: transparent; border: 1px solid rgba(212,168,83,0.2);
color: #8A8070; font-family: 'Pretendard', sans-serif; font-size: 0.8rem;
padding: 0.4rem 1rem; border-radius: 100px; cursor: pointer;
transition: all 0.3s cubic-bezier(0.16,1,0.3,1);
}
.rd-genre-btn:hover { border-color: rgba(212,168,83,0.5); color: #D4A853; }
.rd-genre-btn.active { background: rgba(212,168,83,0.15); border-color: #D4A853; color: #D4A853; }
/* progress bar */
.rd-progress-track { background: rgba(255,255,255,0.06); border-radius: 100px; height: 4px; overflow: hidden; }
.rd-progress-fill { height: 100%; border-radius: 100px; background: linear-gradient(90deg, #D4A853, #F5C842); transition: width 1s cubic-bezier(0.16,1,0.3,1); }
/* marquee */
@keyframes rd-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.rd-marquee-track { animation: rd-marquee 25s linear infinite; display: flex; gap: 0; }
/* hero pulse */
@keyframes rd-pulse { 0%,100% { opacity:0.3; transform: scale(1); } 50% { opacity:0.6; transform: scale(1.05); } }
.rd-orb { animation: rd-pulse 4s ease-in-out infinite; }
/* CTA glow */
.rd-cta-btn {
background: #D4A853; color: #0C0B09; border: none;
font-family: 'Pretendard', sans-serif; font-weight: 700; font-size: 0.9rem;
padding: 0.875rem 2.5rem; border-radius: 100px; cursor: pointer;
letter-spacing: 0.05em;
transition: all 0.3s cubic-bezier(0.16,1,0.3,1);
box-shadow: 0 0 40px rgba(212,168,83,0.3);
}
.rd-cta-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 40px rgba(212,168,83,0.5); background: #E8BC60; }
/* scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: #0C0B09; }
::-webkit-scrollbar-thumb { background: rgba(212,168,83,0.3); border-radius: 2px; }
@media (max-width: 768px) {
.rd-nav { padding: 0.6rem 1.25rem; gap: 1.5rem; }
.rd-bento { grid-template-columns: 1fr !important; }
.rd-hero-title { font-size: clamp(3.5rem, 12vw, 8rem) !important; }
.rd-stats-grid { grid-template-columns: 1fr 1fr !important; gap: 1.5rem !important; }
.rd-quote-grid { grid-template-columns: 1fr !important; }
.rd-currently { grid-template-columns: 1fr !important; }
.rd-monthly-bar { height: 80px !important; }
}
`}} />
{/* ── 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 ── */}
<nav className="rd-nav">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<BookOpenIcon/>
<span className="rd-serif" style={{ fontSize: '1rem', fontWeight: 600, color: '#D4A853' }}> </span>
</div>
<div style={{ display: 'flex', gap: '1.5rem', fontSize: '0.8rem', color: '#8A8070' }}>
{['컬렉션', '독서록', '통계'].map(item => (
<a key={item} href={`#${item}`} style={{ color: 'inherit', textDecoration: 'none', transition: 'color 0.2s' }}
onMouseEnter={e => (e.currentTarget.style.color = '#D4A853')}
onMouseLeave={e => (e.currentTarget.style.color = '#8A8070')}>
{item}
</a>
))}
</div>
</nav>
{/* ── HERO ── */}
<section style={{ minHeight: '100dvh', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 clamp(1.5rem, 6vw, 6rem)', position: 'relative', overflow: 'hidden' }}>
{/* ambient orbs */}
<div className="rd-orb" style={{ position: 'absolute', top: '20%', right: '10%', width: '500px', height: '500px', background: 'radial-gradient(circle, rgba(212,168,83,0.06) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }}/>
<div style={{ position: 'absolute', bottom: '15%', left: '5%', width: '300px', height: '300px', background: 'radial-gradient(circle, rgba(107,91,149,0.06) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }}/>
<div style={{ maxWidth: '1200px', margin: '0 auto', width: '100%', position: 'relative', zIndex: 1 }}>
{/* eyebrow */}
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', border: '1px solid rgba(212,168,83,0.3)', borderRadius: '100px', padding: '0.35rem 0.875rem', marginBottom: '2.5rem' }}>
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: '#D4A853' }}/>
<span style={{ fontSize: '0.72rem', color: '#D4A853', letterSpacing: '0.15em', textTransform: 'uppercase', fontFamily: "'Pretendard', sans-serif" }}>Personal Reading Journal</span>
</div>
{/* hero title */}
<h1 className="rd-hero-title" style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: 'clamp(4.5rem, 13vw, 10rem)', fontWeight: 700, lineHeight: 0.9, letterSpacing: '-0.02em', color: '#F0EAD8', margin: '0 0 2rem', wordBreak: 'keep-all' }}>
<br/>
<span style={{ color: '#D4A853', fontStyle: 'italic' }}> .</span>
</h1>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: '3rem', flexWrap: 'wrap' }}>
<p style={{ fontSize: '1.1rem', color: '#8A8070', maxWidth: '360px', lineHeight: 1.8, wordBreak: 'keep-all', margin: 0 }}>
, , .
</p>
<div style={{ display: 'flex', gap: '1rem' }}>
<button className="rd-cta-btn"> </button>
<button style={{ background: 'transparent', border: '1px solid rgba(212,168,83,0.3)', color: '#D4A853', fontFamily: "'Pretendard', sans-serif", fontSize: '0.9rem', padding: '0.875rem 2rem', borderRadius: '100px', cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.16,1,0.3,1)' }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = 'rgba(212,168,83,0.08)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = 'transparent'; }}>
</button>
</div>
</div>
{/* scroll indicator */}
<div style={{ marginTop: '5rem', display: 'flex', alignItems: 'center', gap: '0.75rem', color: '#4A4035' }}>
<div style={{ width: '40px', height: '1px', background: 'rgba(212,168,83,0.3)' }}/>
<span style={{ fontSize: '0.72rem', letterSpacing: '0.15em', textTransform: 'uppercase' }}>Scroll to explore</span>
</div>
</div>
</section>
{/* ── STATS BAR ── */}
<section style={{ padding: '3rem clamp(1.5rem, 6vw, 6rem)', borderTop: '1px solid rgba(212,168,83,0.1)', borderBottom: '1px solid rgba(212,168,83,0.1)' }}>
<div className="rd-stats-grid rd-reveal" style={{ maxWidth: '1200px', margin: '0 auto', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '2rem', position: 'relative', zIndex: 1 }}>
<StatItem value={47} label="완독한 책" />
<div style={{ width: '1px', background: 'rgba(212,168,83,0.1)', margin: '-0.5rem 0' }}/>
<StatItem value={2340} suffix="p" label="올해 읽은 페이지" />
<div style={{ width: '1px', background: 'rgba(212,168,83,0.1)', margin: '-0.5rem 0' }}/>
<StatItem value={128} label="저장한 문장" />
<div style={{ width: '1px', background: 'rgba(212,168,83,0.1)', margin: '-0.5rem 0' }}/>
<StatItem value={12} label="독서 중인 달" />
</div>
</section>
{/* ── CURRENTLY READING ── */}
<section style={{ padding: '5rem clamp(1.5rem, 6vw, 6rem)' }} id="독서록">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="rd-reveal" style={{ display: 'flex', alignItems: 'baseline', gap: '1rem', marginBottom: '3rem' }}>
<h2 className="rd-serif" style={{ fontSize: '2.5rem', fontWeight: 700, color: '#F0EAD8', margin: 0 }}> </h2>
<span style={{ fontSize: '0.75rem', color: '#8A8070', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Currently reading</span>
</div>
<div className="rd-currently rd-reveal" style={{ display: 'grid', gridTemplateColumns: '280px 1fr', gap: '2.5rem', alignItems: 'stretch' }}>
{/* book cover */}
<div style={{ position: 'relative' }}>
<img src={`https://picsum.photos/seed/${currentlyReading.seed}/280/420`} alt={currentlyReading.title}
style={{ width: '100%', aspectRatio: '2/3', objectFit: 'cover', borderRadius: '12px', filter: 'brightness(0.85) saturate(0.8)', display: 'block' }}/>
<div style={{ position: 'absolute', inset: 0, borderRadius: '12px', boxShadow: 'inset 0 0 0 1px rgba(212,168,83,0.2), -12px 20px 60px rgba(0,0,0,0.6)' }}/>
{/* reading badge */}
<div style={{ position: 'absolute', top: '1rem', left: '-0.5rem', background: '#D4A853', color: '#0C0B09', fontSize: '0.65rem', fontWeight: 700, padding: '4px 10px', borderRadius: '4px', letterSpacing: '0.05em' }}>READING</div>
</div>
{/* info */}
<div className="rd-bezel" style={{ flex: 1 }}>
<div className="rd-bezel-inner" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<div style={{ marginBottom: 'auto' }}>
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem', flexWrap: 'wrap' }}>
<span style={{ fontSize: '0.7rem', color: '#D4A853', border: '1px solid rgba(212,168,83,0.3)', padding: '2px 8px', borderRadius: '4px' }}>{currentlyReading.genre}</span>
<span style={{ fontSize: '0.7rem', color: '#8A8070', border: '1px solid rgba(255,255,255,0.08)', padding: '2px 8px', borderRadius: '4px' }}> {currentlyReading.startDate}</span>
</div>
<h3 className="rd-serif" style={{ fontSize: '2rem', fontWeight: 700, color: '#F0EAD8', margin: '0 0 0.25rem', wordBreak: 'keep-all' }}>{currentlyReading.title}</h3>
<p style={{ fontSize: '0.875rem', color: '#8A8070', margin: '0 0 1.5rem' }}>{currentlyReading.author}</p>
<div style={{ background: 'rgba(212,168,83,0.06)', border: '1px solid rgba(212,168,83,0.15)', borderRadius: '8px', padding: '1rem', marginBottom: '1.5rem' }}>
<p style={{ fontSize: '0.875rem', color: '#C4A060', fontStyle: 'italic', wordBreak: 'keep-all', margin: 0, lineHeight: 1.7 }}>"{currentlyReading.note}"</p>
</div>
</div>
{/* progress */}
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '0.5rem' }}>
<span style={{ fontSize: '0.75rem', color: '#8A8070' }}> </span>
<span style={{ fontSize: '0.75rem', color: '#D4A853', fontFamily: "'Cormorant Garamond', serif", fontWeight: 600 }}>
{currentlyReading.currentPage} / {currentlyReading.totalPages} p {currentlyReading.progress}%
</span>
</div>
<div className="rd-progress-track">
<div className="rd-progress-fill" style={{ width: `${currentlyReading.progress}%` }}/>
</div>
</div>
</div>
</div>
</div>
{/* TBR list */}
<div style={{ marginTop: '2.5rem' }}>
<h3 className="rd-serif rd-reveal" style={{ fontSize: '1.5rem', fontWeight: 600, color: '#6A5840', marginBottom: '1.25rem', margin: '0 0 1.25rem' }}> <span style={{ fontSize: '1rem', fontStyle: 'italic' }}>(TBR)</span></h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
{tbr.map((book, i) => (
<div key={i} className="rd-reveal" style={{ display: 'flex', alignItems: 'center', gap: '1rem', padding: '1rem 1.25rem', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '10px', transition: 'background 0.3s cubic-bezier(0.16,1,0.3,1)' }}
onMouseEnter={e => (e.currentTarget as HTMLElement).style.background = 'rgba(212,168,83,0.04)'}
onMouseLeave={e => (e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.02)'}>
<img src={`https://picsum.photos/seed/${book.seed}/40/60`} alt={book.title} style={{ width: '36px', height: '54px', objectFit: 'cover', borderRadius: '4px', filter: 'brightness(0.8)' }}/>
<div style={{ flex: 1 }}>
<p className="rd-serif" style={{ fontSize: '1rem', fontWeight: 600, color: '#F0EAD8', margin: '0 0 0.2rem' }}>{book.title}</p>
<p style={{ fontSize: '0.75rem', color: '#6A5840', margin: 0 }}>{book.author}</p>
</div>
<span style={{ fontSize: '0.65rem', color: book.priority === '높음' ? '#D4A853' : '#8A8070', border: `1px solid ${book.priority === '높음' ? 'rgba(212,168,83,0.4)' : 'rgba(255,255,255,0.1)'}`, padding: '2px 8px', borderRadius: '4px' }}>{book.priority}</span>
<ArrowUpRight/>
</div>
))}
</div>
</div>
</div>
</section>
{/* ── BOOK COLLECTION ── */}
<section style={{ padding: '2rem clamp(1.5rem, 6vw, 6rem) 5rem' }} id="컬렉션">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="rd-reveal" style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: '2.5rem', flexWrap: 'wrap', gap: '1rem' }}>
<h2 className="rd-serif" style={{ fontSize: '2.5rem', fontWeight: 700, color: '#F0EAD8', margin: 0 }}> </h2>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{genres.map(g => (
<button key={g} className={`rd-genre-btn ${activeGenre === g ? 'active' : ''}`} onClick={() => setActiveGenre(g)}>{g}</button>
))}
</div>
</div>
{/* bento grid */}
<div className="rd-bento" style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr', gap: '1rem' }}>
{filteredBooks.slice(0, 1).map((book, i) => (
<div key={book.seed} style={{ gridRow: 'span 2' }}>
<BookCard book={book} large />
</div>
))}
{filteredBooks.slice(1, 3).map(book => (
<BookCard key={book.seed} book={book} />
))}
{filteredBooks.slice(3, 5).map(book => (
<BookCard key={book.seed} book={book} />
))}
{filteredBooks.slice(5, 7).map((book, i) => (
<div key={book.seed} style={i === 1 ? { gridColumn: 'span 2' } : {}}>
<BookCard book={book} />
</div>
))}
</div>
{filteredBooks.length === 0 && (
<div style={{ textAlign: 'center', padding: '4rem', color: '#4A4035' }}>
<p className="rd-serif" style={{ fontSize: '1.5rem' }}> .</p>
</div>
)}
</div>
</section>
{/* ── QUOTES MASONRY ── */}
<section style={{ padding: '5rem clamp(1.5rem, 6vw, 6rem)', background: 'rgba(212,168,83,0.03)', borderTop: '1px solid rgba(212,168,83,0.08)', borderBottom: '1px solid rgba(212,168,83,0.08)' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="rd-reveal" style={{ marginBottom: '3rem' }}>
<p style={{ fontSize: '0.72rem', color: '#D4A853', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: '0.75rem' }}>Highlighted Quotes</p>
<h2 className="rd-serif" style={{ fontSize: '2.5rem', fontWeight: 700, color: '#F0EAD8', margin: 0 }}> </h2>
</div>
<div className="rd-quote-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '1.25rem' }}>
{quotes.map((quote, i) => (
<div key={i} className={`rd-bezel rd-reveal ${i === 1 ? 'rd-reveal' : ''}`}
style={{ marginTop: i % 2 === 1 ? '2rem' : '0' }}>
<div className="rd-bezel-inner">
<QuoteIcon/>
<p className="rd-serif" style={{ fontSize: '1.2rem', color: '#C4B090', lineHeight: 1.8, margin: '1rem 0 1.25rem', fontStyle: 'italic', wordBreak: 'keep-all' }}>
{quote.text}
</p>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<p style={{ fontSize: '0.75rem', color: '#6A5840', margin: 0 }}>{quote.author}</p>
<span style={{ fontSize: '0.65rem', color: '#D4A853', border: '1px solid rgba(212,168,83,0.3)', padding: '2px 8px', borderRadius: '4px' }}>{quote.genre}</span>
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* ── MONTHLY STATS ── */}
<section style={{ padding: '5rem clamp(1.5rem, 6vw, 6rem)' }} id="통계">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="rd-reveal" style={{ marginBottom: '3rem' }}>
<p style={{ fontSize: '0.72rem', color: '#D4A853', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: '0.75rem' }}>Reading Rhythm</p>
<h2 className="rd-serif" style={{ fontSize: '2.5rem', fontWeight: 700, color: '#F0EAD8', margin: 0 }}> </h2>
</div>
<div className="rd-bezel rd-reveal">
<div className="rd-bezel-inner">
<div style={{ display: 'flex', alignItems: 'flex-end', gap: '1rem', height: '160px' }}>
{monthlyData.map((d, i) => (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem', height: '100%', justifyContent: 'flex-end' }}>
<span style={{ fontSize: '0.75rem', color: '#D4A853', fontFamily: "'Cormorant Garamond', serif", fontWeight: 600 }}>{d.books}</span>
<div style={{ width: '100%', background: 'rgba(212,168,83,0.12)', borderRadius: '6px 6px 0 0', height: `${(d.books / maxBooks) * 100}%`, position: 'relative', transition: 'height 0.8s cubic-bezier(0.16,1,0.3,1)', minHeight: '8px' }}>
<div style={{ position: 'absolute', inset: 0, borderRadius: '6px 6px 0 0', background: `linear-gradient(to top, rgba(212,168,83,0.6), rgba(212,168,83,0.2))` }}/>
</div>
<span style={{ fontSize: '0.72rem', color: '#6A5840' }}>{d.month}</span>
</div>
))}
</div>
<div style={{ marginTop: '2rem', paddingTop: '1.5rem', borderTop: '1px solid rgba(255,255,255,0.05)', display: 'flex', gap: '2.5rem', flexWrap: 'wrap' }}>
{[
{ label: '총 완독 권수', value: '15권', sub: '최근 6개월' },
{ label: '평균', value: '2.5권', sub: '월 평균' },
{ label: '최다 독서 달', value: '1월', sub: '4권 완독' },
].map((item, i) => (
<div key={i}>
<p className="rd-serif" style={{ fontSize: '1.75rem', fontWeight: 700, color: '#D4A853', margin: '0 0 0.2rem' }}>{item.value}</p>
<p style={{ fontSize: '0.75rem', color: '#8A8070', margin: 0 }}>{item.label} · <span style={{ color: '#6A5840' }}>{item.sub}</span></p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* ── BRAND MARQUEE ── */}
<div style={{ borderTop: '1px solid rgba(212,168,83,0.08)', borderBottom: '1px solid rgba(212,168,83,0.08)', overflow: 'hidden', padding: '1rem 0' }}>
<div className="rd-marquee-track">
{[...Array(2)].map((_, i) => (
<div key={i} style={{ display: 'flex', gap: '0', whiteSpace: 'nowrap' }}>
{['파친코', '어린 왕자', '원씽', '채식주의자', '노인과 바다', '사피엔스', '총·균·쇠', '코스모스', '82년생 김지영'].map((title, j) => (
<span key={j} style={{ padding: '0 2rem', fontSize: '0.8rem', color: '#4A4035', letterSpacing: '0.1em', textTransform: 'uppercase' }}>
{title} <span style={{ color: 'rgba(212,168,83,0.3)', margin: '0 1rem' }}>·</span>
</span>
))}
</div>
))}
</div>
</div>
{/* ── CTA ── */}
<section style={{ padding: '6rem clamp(1.5rem, 6vw, 6rem)', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', width: '600px', height: '600px', background: 'radial-gradient(circle, rgba(212,168,83,0.08) 0%, transparent 70%)', pointerEvents: 'none' }}/>
<div className="rd-reveal" style={{ maxWidth: '700px', margin: '0 auto', textAlign: 'center', position: 'relative', zIndex: 1 }}>
<p style={{ fontSize: '0.72rem', color: '#D4A853', letterSpacing: '0.15em', textTransform: 'uppercase', marginBottom: '1.5rem' }}> </p>
<h2 className="rd-serif" style={{ fontSize: 'clamp(2.5rem, 6vw, 4rem)', fontWeight: 700, color: '#F0EAD8', margin: '0 0 1.5rem', lineHeight: 1.1, wordBreak: 'keep-all' }}>
<br/>
<span style={{ color: '#D4A853', fontStyle: 'italic' }}> .</span>
</h2>
<p style={{ fontSize: '1rem', color: '#6A5840', maxWidth: '400px', margin: '0 auto 2.5rem', lineHeight: 1.8, wordBreak: 'keep-all' }}>
, , .
</p>
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', flexWrap: 'wrap' }}>
<button className="rd-cta-btn"> </button>
<button style={{ background: 'transparent', border: '1px solid rgba(212,168,83,0.3)', color: '#D4A853', fontFamily: "'Pretendard', sans-serif", fontSize: '0.9rem', padding: '0.875rem 2rem', borderRadius: '100px', cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.16,1,0.3,1)' }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = 'rgba(212,168,83,0.08)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = 'transparent'; }}>
</button>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer style={{ borderTop: '1px solid rgba(212,168,83,0.08)', padding: '2rem clamp(1.5rem, 6vw, 6rem)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '1rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<BookOpenIcon/>
<span className="rd-serif" style={{ color: '#D4A853', fontWeight: 600 }}> </span>
</div>
<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: '5.5rem', 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>
</>
);
}

View File

@@ -1,770 +0,0 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
/* ═══════════════════════════════════════════════════
DESIGN TOKEN — 인테리어 페이지와 완전히 다른 팔레트
No gold, no cream, no warm amber.
Editorial B&W + cool stone.
═══════════════════════════════════════════════════ */
const T = {
ink: '#0C0B09',
paper: '#F4F2EF',
sand: '#E9E5DF',
stone: '#7C7870',
chalk: '#B8B3AB',
white: '#FAFAF8',
} as const;
const BANNER_H = 40;
const NAV_H = 56;
/* ═══════════════════════════════════════════════════
FASHION IMAGES — picsum.photos (안정적, 항상 로드됨)
seed 값은 항상 동일한 이미지를 반환하므로 일관성 유지
═══════════════════════════════════════════════════ */
const IMG = {
/* 히어로 — 세로형 풀스크린 */
hero: 'https://picsum.photos/seed/mellow-hero/900/1200',
/* 상품 — 세로 portrait (3:4) */
p1: 'https://picsum.photos/seed/mellow-p1/480/640',
p2: 'https://picsum.photos/seed/mellow-p2/480/640',
p3: 'https://picsum.photos/seed/mellow-p3/480/640',
p4: 'https://picsum.photos/seed/mellow-p4/480/640',
p5: 'https://picsum.photos/seed/mellow-p5/480/640',
p6: 'https://picsum.photos/seed/mellow-p6/480/640',
p7: 'https://picsum.photos/seed/mellow-p7/480/640',
p8: 'https://picsum.photos/seed/mellow-p8/480/640',
/* 에디토리얼 배너 — 와이드 */
edit1: 'https://picsum.photos/seed/mellow-edit/1400/700',
/* 브랜드 스토리 이미지 */
feat1: 'https://picsum.photos/seed/mellow-feat1/600/800',
feat2: 'https://picsum.photos/seed/mellow-feat2/600/800',
/* 룩북 — portrait strip */
lb1: 'https://picsum.photos/seed/mellow-lb1/400/533',
lb2: 'https://picsum.photos/seed/mellow-lb2/400/533',
lb3: 'https://picsum.photos/seed/mellow-lb3/400/533',
lb4: 'https://picsum.photos/seed/mellow-lb4/400/533',
lb5: 'https://picsum.photos/seed/mellow-lb5/400/533',
} as const;
/* ═══════════════════════════════════════════════════
DATA
═══════════════════════════════════════════════════ */
type Cat = '전체' | '아우터' | '상의' | '하의' | '액세서리';
const products = [
{ id: 1, name: 'Structured Blazer', kr: '스트럭처드 블레이저', price: 328000, cat: '아우터' as Cat, img: IMG.p1, isNew: true },
{ id: 2, name: 'Cocoon Coat', kr: '코쿤 코트', price: 498000, cat: '아우터' as Cat, img: IMG.p2, isBest: true },
{ id: 3, name: 'Slip Midi Dress', kr: '슬립 미디 드레스', price: 218000, cat: '상의' as Cat, img: IMG.p3, isNew: true },
{ id: 4, name: 'Relaxed Oxford', kr: '릴랙스드 옥스포드', price: 145000, cat: '상의' as Cat, img: IMG.p4 },
{ id: 5, name: 'Wide Trousers', kr: '와이드 트라우저', price: 185000, cat: '하의' as Cat, img: IMG.p5 },
{ id: 6, name: 'Barrel Denim', kr: '배럴 데님', price: 198000, cat: '하의' as Cat, img: IMG.p6, isBest: true },
{ id: 7, name: 'Leather Mini Bag', kr: '레더 미니백', price: 278000, cat: '액세서리' as Cat, img: IMG.p7, isNew: true },
{ id: 8, name: 'Square Sunglasses', kr: '스퀘어 선글라스', price: 128000, cat: '액세서리' as Cat, img: IMG.p8 },
];
const reviews = [
{ quote: '입고 나서 동료가 어디서 샀냐고 계속 물어봐요. 핏이 정말 완벽합니다.', name: '하윤서', city: '서울', item: 'Structured Blazer' },
{ quote: '코쿤 코트, 사진보다 실물이 훨씬 좋아요. 소재감이 기대 이상입니다.', name: '이서진', city: '제주', item: 'Cocoon Coat' },
{ quote: '슬립 드레스를 받았는데 바느질 하나하나에서 정성이 느껴져요.', name: '박도현', city: '부산', item: 'Slip Midi Dress' },
];
/* ═══════════════════════════════════════════════════
MAIN PAGE
═══════════════════════════════════════════════════ */
export default function ShoppingPage() {
const [activeCat, setActiveCat] = useState<Cat | '전체'>('전체');
const [scrolled, setScrolled] = useState(false);
const [showTop, setShowTop] = useState(false);
const [cart, setCart] = useState(0);
useEffect(() => {
const sc: HTMLElement =
(document.querySelector('.main-content') as HTMLElement | null) ??
document.documentElement;
const onScroll = () => {
setScrolled(sc.scrollTop > 40);
setShowTop(sc.scrollTop > 500);
};
sc.addEventListener('scroll', onScroll, { passive: true });
/* reveal */
const obs = new IntersectionObserver(
(entries) => entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('ml-in'); }),
{ threshold: 0.07, root: sc === document.documentElement ? null : sc }
);
document.querySelectorAll('.ml-reveal').forEach(el => obs.observe(el));
return () => {
sc.removeEventListener('scroll', onScroll);
obs.disconnect();
};
}, []);
const cats: (Cat | '전체')[] = ['전체', '아우터', '상의', '하의', '액세서리'];
const filtered = activeCat === '전체' ? products : products.filter(p => p.cat === activeCat);
const scrollTop = () => {
const sc = (document.querySelector('.main-content') as HTMLElement | null) ?? document.documentElement;
sc.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<div className="ml-page" style={{ fontFamily: "'Pretendard','Apple SD Gothic Neo',system-ui,sans-serif", background: T.paper, color: T.ink, overflowX: 'hidden' }}>
{/* ── FONTS + GLOBAL ── */}
<style dangerouslySetInnerHTML={{ __html: `
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&display=swap');
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.min.css');
/* 전역 리셋을 .ml-page 하위로 한정 — 사이드바 오염 방지 */
.ml-page, .ml-page *, .ml-page *::before, .ml-page *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* scroll reveal */
.ml-reveal {
opacity: 0; transform: translateY(28px);
transition: opacity .75s cubic-bezier(.16,1,.3,1), transform .75s cubic-bezier(.16,1,.3,1);
}
.ml-reveal.ml-in { opacity: 1; transform: none; }
.ml-reveal > *:nth-child(2) { transition-delay: 90ms; }
.ml-reveal > *:nth-child(3) { transition-delay: 180ms; }
.ml-reveal > *:nth-child(4) { transition-delay: 270ms; }
.ml-reveal > *:nth-child(5) { transition-delay: 360ms; }
.ml-reveal > *:nth-child(6) { transition-delay: 450ms; }
.ml-reveal > *:nth-child(7) { transition-delay: 540ms; }
.ml-reveal > *:nth-child(8) { transition-delay: 630ms; }
@keyframes ml-fadeUp {
from { opacity: 0; transform: translateY(2rem); }
to { opacity: 1; transform: none; }
}
@keyframes ml-ticker {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes ml-float {
0%,100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
/* product card hover */
.ml-card-img {
transition: transform .6s cubic-bezier(.16,1,.3,1), filter .4s;
}
.ml-card:hover .ml-card-img { transform: scale(1.04); filter: brightness(0.92); }
/* lookbook scroll */
.ml-lb { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 8px; scrollbar-width: none; }
.ml-lb::-webkit-scrollbar { display: none; }
/* nav underline */
.ml-nav-a {
position: relative; font-size: 12px; font-weight: 500;
letter-spacing: .06em; text-decoration: none; color: inherit;
transition: opacity .25s;
}
.ml-nav-a::after {
content: ''; position: absolute; bottom: -2px; left: 0; right: 0;
height: 1px; background: currentColor; transform: scaleX(0);
transform-origin: left; transition: transform .35s cubic-bezier(.16,1,.3,1);
}
.ml-nav-a:hover::after { transform: scaleX(1); }
/* cat pill */
.ml-pill {
border: 1px solid rgba(12,11,9,.18); border-radius: 0;
background: transparent; font-family: inherit; font-size: 11px;
font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
padding: 8px 20px; cursor: pointer; color: ${T.stone};
transition: all .3s cubic-bezier(.16,1,.3,1);
}
.ml-pill:hover { border-color: ${T.ink}; color: ${T.ink}; }
.ml-pill.on { background: ${T.ink}; color: ${T.white}; border-color: ${T.ink}; }
/* ticker */
.ml-ticker-track { animation: ml-ticker 32s linear infinite; display: flex; white-space: nowrap; }
/* scrollbar */
::-webkit-scrollbar { width: 3px; }
::-webkit-scrollbar-track { background: ${T.paper}; }
::-webkit-scrollbar-thumb { background: ${T.chalk}; border-radius: 2px; }
@media (max-width:1024px) {
.ml-hero-grid { grid-template-columns: 1fr !important; }
.ml-hero-img { display: none !important; }
.ml-feat-grid { grid-template-columns: 1fr !important; }
}
@media (max-width:768px) {
.ml-prod-grid { grid-template-columns: 1fr 1fr !important; }
}
@media (max-width:480px) {
.ml-prod-grid { grid-template-columns: 1fr !important; }
}
`}} />
{/* ════════════════════════════════════════════
BACK BANNER
════════════════════════════════════════════ */}
<div style={{
background: T.ink, height: BANNER_H,
display: 'flex', alignItems: 'center', padding: '0 32px', gap: 14,
}}>
<Link href="/services/website"
style={{ fontSize: 12, color: T.chalk, textDecoration: 'none', letterSpacing: '.04em', transition: 'color .2s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = T.white)}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = T.chalk)}>
</Link>
<span style={{ color: T.stone, fontSize: 11 }}>|</span>
<span style={{ fontSize: 11, color: T.stone, fontStyle: 'italic', fontFamily: "'DM Serif Display', serif" }}>
SAMPLE ·
</span>
</div>
{/* ════════════════════════════════════════════
NAV — minimal hairline
════════════════════════════════════════════ */}
<nav style={{
position: 'sticky', top: 0, zIndex: 50, height: NAV_H,
display: 'flex', alignItems: 'center', padding: '0 48px',
background: scrolled ? `rgba(244,242,239,.96)` : T.ink,
backdropFilter: 'blur(16px)',
borderBottom: `1px solid ${scrolled ? 'rgba(12,11,9,.1)' : 'rgba(255,255,255,.06)'}`,
transition: 'background .4s cubic-bezier(.16,1,.3,1), border-color .4s',
}}>
<div style={{ maxWidth: 1280, margin: '0 auto', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{/* wordmark */}
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 18, fontWeight: 400, letterSpacing: '.12em', color: scrolled ? T.ink : T.white, transition: 'color .4s' }}>
MELLOW
</div>
{/* links */}
<div style={{ display: 'flex', gap: 36 }}>
{['신상품', '아우터', '상의 / 하의', '액세서리', '세일'].map(l => (
<a key={l} href="#collection" className="ml-nav-a"
style={{ color: scrolled ? T.stone : 'rgba(244,242,239,.6)' }}>{l}</a>
))}
</div>
{/* actions */}
<div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
{/* search */}
<button style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4,
color: scrolled ? T.stone : 'rgba(244,242,239,.6)', transition: 'color .3s' }}
aria-label="검색">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="15.65" y2="15.65"/>
</svg>
</button>
{/* cart */}
<button onClick={() => setCart(c => c + 1)}
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, position: 'relative',
color: scrolled ? T.ink : T.white, transition: 'color .3s' }}
aria-label="장바구니">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/>
<line x1="3" y1="6" x2="21" y2="6"/>
<path d="M16 10a4 4 0 01-8 0"/>
</svg>
{cart > 0 && (
<span style={{
position: 'absolute', top: -5, right: -5,
width: 16, height: 16, borderRadius: '50%',
background: scrolled ? T.ink : T.white,
color: scrolled ? T.white : T.ink,
fontSize: 9, fontWeight: 800,
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>{cart}</span>
)}
</button>
</div>
</div>
</nav>
{/* ════════════════════════════════════════════
HERO — editorial dark split
════════════════════════════════════════════ */}
<section style={{
background: T.ink,
minHeight: `calc(100dvh - ${BANNER_H}px - ${NAV_H}px)`,
display: 'grid', gridTemplateColumns: '1fr 1fr',
overflow: 'hidden',
}} className="ml-hero-grid">
{/* Left */}
<div style={{
display: 'flex', flexDirection: 'column', justifyContent: 'flex-end',
padding: '80px 64px 72px 80px',
animation: 'ml-fadeUp .9s cubic-bezier(.16,1,.3,1) both',
}}>
<p style={{ fontSize: 10, color: T.stone, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 40 }}>
2025 Spring / Summer Collection
</p>
<h1 style={{
fontFamily: "'DM Serif Display', serif", fontStyle: 'italic',
fontSize: 'clamp(3.5rem, 6vw, 6.5rem)',
lineHeight: 1.0, color: T.white, marginBottom: 40,
letterSpacing: '-.02em', wordBreak: 'keep-all',
}}>
Clothes<br/>that last<br/>
<span style={{ color: T.sand, fontStyle: 'normal' }}>longer than</span><br/>
trends.
</h1>
<p style={{ fontSize: 14, color: T.stone, lineHeight: 1.9, marginBottom: 44, maxWidth: 360, wordBreak: 'keep-all' }}>
, , .<br/>
.
</p>
<div style={{ display: 'flex', gap: 14 }}>
<a href="#collection" style={{
display: 'inline-block', padding: '14px 32px',
background: T.white, color: T.ink,
fontSize: 12, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase',
textDecoration: 'none',
transition: 'opacity .25s',
}}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '.82')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}>
</a>
<a href="#story" style={{
display: 'inline-block', padding: '14px 28px',
border: '1px solid rgba(244,242,239,.18)', color: 'rgba(244,242,239,.6)',
fontSize: 12, fontWeight: 500, letterSpacing: '.08em', textTransform: 'uppercase',
textDecoration: 'none', transition: 'border-color .3s, color .3s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(244,242,239,.4)'; (e.currentTarget as HTMLElement).style.color = T.white; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(244,242,239,.18)'; (e.currentTarget as HTMLElement).style.color = 'rgba(244,242,239,.6)'; }}>
</a>
</div>
{/* inline stats */}
<div style={{ display: 'flex', gap: 48, marginTop: 64, paddingTop: 40, borderTop: '1px solid rgba(244,242,239,.08)' }}>
{[['1,247+','누적 고객'],['4.87','평점'],['98%','재구매율']].map(([n,l]) => (
<div key={l}>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 22, color: T.white, letterSpacing: '-.01em' }}>{n}</div>
<div style={{ fontSize: 10, color: T.stone, marginTop: 4, letterSpacing: '.08em' }}>{l}</div>
</div>
))}
</div>
</div>
{/* Right — image */}
<div className="ml-hero-img" style={{ position: 'relative', overflow: 'hidden' }}>
<img src={IMG.hero} alt="SS25 Collection"
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', filter: 'brightness(.75)' }}
loading="eager"
/>
{/* Caption */}
<div style={{
position: 'absolute', bottom: 32, left: 32, right: 32,
display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end',
}}>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 11, color: 'rgba(244,242,239,.5)', letterSpacing: '.18em', textTransform: 'uppercase' }}>
Look 01 / Structured Blazer
</div>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 13, color: T.white }}>
328,000
</div>
</div>
{/* Floating badge */}
<div style={{
position: 'absolute', top: 32, right: 32,
padding: '10px 14px',
background: 'rgba(12,11,9,.7)', backdropFilter: 'blur(12px)',
border: '1px solid rgba(244,242,239,.1)',
animation: 'ml-float 5s ease-in-out infinite',
}}>
<div style={{ fontSize: 9, color: T.stone, letterSpacing: '.18em', textTransform: 'uppercase', marginBottom: 4 }}> </div>
<div style={{ fontSize: 12, color: T.white, fontWeight: 600 }}>2025 S/S</div>
</div>
</div>
</section>
{/* ════════════════════════════════════════════
TICKER
════════════════════════════════════════════ */}
<div style={{ background: T.sand, borderTop: '1px solid rgba(12,11,9,.08)', borderBottom: '1px solid rgba(12,11,9,.08)', overflow: 'hidden', padding: '11px 0' }}>
<div className="ml-ticker-track">
{[...Array(2)].map((_, i) => (
<span key={i} style={{ flexShrink: 0 }}>
{['Free Shipping Over 50,000', '무료 배송', '7일 무료 반품', '국내 장인 제작', '천연 소재만 사용', 'No Trend — Just Quality'].map((t, j) => (
<span key={j} style={{ padding: '0 2rem', fontSize: 11, color: T.stone, letterSpacing: '.12em', textTransform: 'uppercase' }}>
{t}<span style={{ color: T.chalk, margin: '0 .5rem' }}> · </span>
</span>
))}
</span>
))}
</div>
</div>
{/* ════════════════════════════════════════════
COLLECTION GRID
════════════════════════════════════════════ */}
<section id="collection" style={{ padding: '80px 48px', maxWidth: 1360, margin: '0 auto' }}>
{/* Header row */}
<div className="ml-reveal" style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 40, flexWrap: 'wrap', gap: 20 }}>
<div>
<h2 style={{ fontFamily: "'DM Serif Display', serif", fontSize: 'clamp(1.8rem,3vw,2.8rem)', fontWeight: 400, color: T.ink, letterSpacing: '-.01em' }}>
{activeCat === '전체' ? 'All Collection' : activeCat}
</h2>
<p style={{ fontSize: 12, color: T.stone, marginTop: 4, letterSpacing: '.04em' }}>
{filtered.length}
</p>
</div>
<div style={{ display: 'flex', gap: 4 }}>
{cats.map(c => (
<button key={c} className={`ml-pill${activeCat === c ? ' on' : ''}`}
onClick={() => setActiveCat(c)}>{c}</button>
))}
</div>
</div>
{/* Grid — asymmetric sizing */}
<div className="ml-prod-grid ml-reveal" style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '4px',
}}>
{filtered.map((p, i) => (
<div key={p.id} className="ml-card" style={{
cursor: 'pointer',
gridRow: (i === 0 || i === 5) ? 'span 2' : 'span 1',
}}>
{/* Image */}
<div style={{
overflow: 'hidden',
aspectRatio: (i === 0 || i === 5) ? '3/5' : '3/4',
background: T.sand, position: 'relative',
}}>
<img src={p.img} alt={p.kr} className="ml-card-img"
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
loading="lazy"
/>
{/* Badges */}
{(p.isNew || p.isBest) && (
<div style={{
position: 'absolute', top: 14, left: 14,
background: p.isBest ? T.ink : 'transparent',
border: `1px solid ${p.isBest ? T.ink : 'rgba(12,11,9,.35)'}`,
color: p.isBest ? T.white : T.ink,
fontSize: 9, fontWeight: 700, letterSpacing: '.12em', textTransform: 'uppercase',
padding: '4px 9px',
}}>
{p.isBest ? 'BEST' : 'NEW'}
</div>
)}
{/* Quick add */}
<div style={{
position: 'absolute', bottom: 0, left: 0, right: 0,
background: 'rgba(12,11,9,.82)', backdropFilter: 'blur(4px)',
padding: '13px 0', textAlign: 'center',
opacity: 0, transition: 'opacity .3s',
}} className="ml-quick">
<button
onClick={() => setCart(c => c + 1)}
style={{
background: 'none', border: 'none', cursor: 'pointer',
color: T.white, fontSize: 11, fontWeight: 600, letterSpacing: '.1em', textTransform: 'uppercase',
fontFamily: 'inherit',
}}>
+
</button>
</div>
</div>
{/* Info */}
<div style={{ padding: '14px 2px 24px' }}>
<p style={{ fontSize: 10, color: T.stone, letterSpacing: '.1em', textTransform: 'uppercase', marginBottom: 5 }}>{p.cat}</p>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
<div>
<p style={{ fontSize: 14, fontWeight: 500, color: T.ink, marginBottom: 2, wordBreak: 'keep-all', letterSpacing: '-.01em' }}>{p.name}</p>
<p style={{ fontSize: 11, color: T.chalk, wordBreak: 'keep-all' }}>{p.kr}</p>
</div>
<p style={{ fontSize: 14, fontWeight: 600, color: T.ink, flexShrink: 0, letterSpacing: '-.01em' }}>
{p.price.toLocaleString()}<span style={{ fontSize: 11, fontWeight: 400 }}></span>
</p>
</div>
</div>
</div>
))}
</div>
<div className="ml-reveal" style={{ textAlign: 'center', marginTop: 56 }}>
<button style={{
border: '1px solid rgba(12,11,9,.2)', background: 'transparent',
color: T.ink, fontFamily: 'inherit', fontSize: 11, fontWeight: 600,
letterSpacing: '.12em', textTransform: 'uppercase', padding: '14px 48px',
cursor: 'pointer', transition: 'background .3s, color .3s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = T.ink; (e.currentTarget as HTMLElement).style.color = T.white; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = 'transparent'; (e.currentTarget as HTMLElement).style.color = T.ink; }}>
(24)
</button>
</div>
</section>
{/* ════════════════════════════════════════════
EDITORIAL BANNER — full-bleed dark
════════════════════════════════════════════ */}
<section style={{ position: 'relative', height: 560, overflow: 'hidden', margin: '0 0 0 0' }}>
<img src={IMG.edit1} alt="Editorial"
style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'brightness(.45)' }}
loading="lazy"
/>
<div className="ml-reveal" style={{
position: 'absolute', inset: 0, display: 'flex',
flexDirection: 'column', justifyContent: 'center', alignItems: 'center',
padding: '40px',
}}>
<p style={{ fontSize: 10, color: T.chalk, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 24 }}>
Look Book 2025 S/S
</p>
<h2 style={{
fontFamily: "'DM Serif Display', serif", fontStyle: 'italic',
fontSize: 'clamp(2.5rem,5vw,5rem)', color: T.white,
textAlign: 'center', lineHeight: 1.1, wordBreak: 'keep-all', maxWidth: 800,
}}>
Dressed for<br/>the life you live.
</h2>
<a href="#collection" style={{
marginTop: 40, display: 'inline-block', padding: '13px 36px',
border: '1px solid rgba(244,242,239,.4)', color: T.white,
fontSize: 11, letterSpacing: '.14em', textTransform: 'uppercase', textDecoration: 'none',
transition: 'background .3s, border-color .3s',
}}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = 'rgba(244,242,239,.12)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = 'transparent'; }}>
</a>
</div>
</section>
{/* ════════════════════════════════════════════
BRAND STORY — 2-col asymmetric
════════════════════════════════════════════ */}
<section id="story" style={{ padding: '100px 80px', maxWidth: 1280, margin: '0 auto' }}>
<div className="ml-feat-grid ml-reveal" style={{ display: 'grid', gridTemplateColumns: '5fr 4fr', gap: '80px', alignItems: 'center' }}>
{/* Images side-by-side */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
<div style={{ overflow: 'hidden', aspectRatio: '3/4', background: T.sand }}>
<img src={IMG.feat1} alt="Brand story 1"
style={{ width: '100%', height: '100%', objectFit: 'cover', filter: 'saturate(.7) brightness(.95)', transition: 'transform .7s cubic-bezier(.16,1,.3,1)' }}
loading="lazy"
onMouseEnter={e => ((e.currentTarget as HTMLImageElement).style.transform = 'scale(1.03)')}
onMouseLeave={e => ((e.currentTarget as HTMLImageElement).style.transform = 'scale(1)')}
/>
</div>
<div style={{ overflow: 'hidden', aspectRatio: '3/4', background: T.sand, marginTop: 40 }}>
<img src={IMG.feat2} alt="Brand story 2"
style={{ width: '100%', height: '100%', objectFit: 'cover', filter: 'saturate(.7) brightness(.95)', transition: 'transform .7s cubic-bezier(.16,1,.3,1)' }}
loading="lazy"
onMouseEnter={e => ((e.currentTarget as HTMLImageElement).style.transform = 'scale(1.03)')}
onMouseLeave={e => ((e.currentTarget as HTMLImageElement).style.transform = 'scale(1)')}
/>
</div>
</div>
{/* Text */}
<div style={{ paddingLeft: 16 }}>
<p style={{ fontSize: 10, color: T.stone, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 28 }}>
About MELLOW
</p>
<h2 style={{ fontFamily: "'DM Serif Display', serif", fontSize: 'clamp(2rem,3.5vw,3.2rem)', fontWeight: 400, lineHeight: 1.15, color: T.ink, marginBottom: 28, wordBreak: 'keep-all', letterSpacing: '-.01em' }}>
<br/> .
</h2>
<p style={{ fontSize: 14, color: T.stone, lineHeight: 1.95, marginBottom: 18, wordBreak: 'keep-all' }}>
2019, .
, 5 .
</p>
<p style={{ fontSize: 14, color: T.stone, lineHeight: 1.95, marginBottom: 40, wordBreak: 'keep-all' }}>
. ·· ,
.
</p>
<div style={{ display: 'flex', gap: 40 }}>
{[['6년', '브랜드 히스토리'],['100%','천연 소재'],['소량','국내 생산']].map(([n,l]) => (
<div key={l}>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 24, color: T.ink }}>{n}</div>
<div style={{ fontSize: 11, color: T.chalk, marginTop: 4, wordBreak: 'keep-all', letterSpacing: '.04em' }}>{l}</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* ════════════════════════════════════════════
LOOKBOOK — horizontal scroll strip
════════════════════════════════════════════ */}
<section style={{ padding: '0 48px 80px', maxWidth: 1360, margin: '0 auto' }}>
<div className="ml-reveal" style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 24 }}>
<h3 style={{ fontFamily: "'DM Serif Display', serif", fontSize: 20, fontWeight: 400, color: T.ink }}>Lookbook</h3>
<a href="#" style={{ fontSize: 11, color: T.stone, letterSpacing: '.1em', textTransform: 'uppercase', textDecoration: 'none', transition: 'color .2s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = T.ink)}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = T.stone)}>
</a>
</div>
<div className="ml-lb">
{[IMG.lb1, IMG.lb2, IMG.lb3, IMG.lb4, IMG.lb5].map((src, i) => (
<div key={i} style={{
flexShrink: 0, width: 240, aspectRatio: '3/4', overflow: 'hidden',
background: T.sand, cursor: 'pointer',
}}>
<img src={src} alt={`Lookbook ${i+1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover', filter: 'saturate(.65)', transition: 'transform .6s cubic-bezier(.16,1,.3,1), filter .4s' }}
loading="lazy"
onMouseEnter={e => { (e.currentTarget as HTMLImageElement).style.transform = 'scale(1.04)'; (e.currentTarget as HTMLImageElement).style.filter = 'saturate(.85)'; }}
onMouseLeave={e => { (e.currentTarget as HTMLImageElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLImageElement).style.filter = 'saturate(.65)'; }}
/>
</div>
))}
</div>
</section>
{/* ════════════════════════════════════════════
REVIEWS — typographic, no card bg
════════════════════════════════════════════ */}
<section style={{ background: T.ink, padding: '96px 80px' }}>
<div style={{ maxWidth: 1100, margin: '0 auto' }}>
<p className="ml-reveal" style={{ fontSize: 10, color: T.stone, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 64 }}>
Customer Notes
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '1px', background: 'rgba(244,242,239,.06)' }}>
{reviews.map((r, i) => (
<div key={i} className="ml-reveal" style={{ padding: '40px 36px', background: T.ink }}>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 11, color: T.stone, letterSpacing: '.14em', textTransform: 'uppercase', marginBottom: 24 }}>
{r.item}
</div>
<p style={{ fontSize: 16, color: 'rgba(244,242,239,.85)', lineHeight: 1.75, marginBottom: 32, wordBreak: 'keep-all', fontStyle: 'italic', fontFamily: "'DM Serif Display', serif" }}>
"{r.quote}"
</p>
<div style={{ borderTop: '1px solid rgba(244,242,239,.07)', paddingTop: 20 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: T.chalk }}>{r.name}</div>
<div style={{ fontSize: 11, color: T.stone, marginTop: 2 }}>{r.city}</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* ════════════════════════════════════════════
CTA — minimal centered on paper
════════════════════════════════════════════ */}
<section style={{ padding: '100px 48px', textAlign: 'center', background: T.paper }}>
<div className="ml-reveal" style={{ maxWidth: 540, margin: '0 auto' }}>
<p style={{ fontSize: 10, color: T.stone, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 24 }}>
Newsletter
</p>
<h2 style={{ fontFamily: "'DM Serif Display', serif", fontSize: 'clamp(2rem,4vw,3rem)', fontWeight: 400, color: T.ink, lineHeight: 1.2, marginBottom: 20, wordBreak: 'keep-all' }}>
<br/>
</h2>
<p style={{ fontSize: 13, color: T.stone, lineHeight: 1.8, marginBottom: 36, wordBreak: 'keep-all' }}>
10% .
</p>
<div style={{ display: 'flex', gap: 0, maxWidth: 420, margin: '0 auto' }}>
<input type="email" placeholder="이메일 주소"
style={{
flex: 1, padding: '13px 18px',
border: '1px solid rgba(12,11,9,.18)', borderRight: 'none',
background: 'transparent', fontFamily: 'inherit', fontSize: 13,
color: T.ink, outline: 'none',
}}
onFocus={e => (e.currentTarget.style.borderColor = T.ink)}
onBlur={e => (e.currentTarget.style.borderColor = 'rgba(12,11,9,.18)')}
/>
<button style={{
padding: '13px 22px', background: T.ink, color: T.white,
border: '1px solid transparent', fontFamily: 'inherit', fontSize: 11,
fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase',
cursor: 'pointer', flexShrink: 0, transition: 'opacity .2s',
}}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '.8')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}>
</button>
</div>
</div>
</section>
{/* ════════════════════════════════════════════
FOOTER
════════════════════════════════════════════ */}
<footer style={{ background: T.ink, padding: '64px 80px 36px', color: T.stone }}>
<div style={{ maxWidth: 1280, margin: '0 auto' }}>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr 1fr', gap: 48, marginBottom: 56 }}>
<div>
<div style={{ fontFamily: "'DM Serif Display', serif", fontSize: 20, color: T.white, letterSpacing: '.1em', marginBottom: 6 }}>MELLOW</div>
<div style={{ fontSize: 9, color: T.stone, letterSpacing: '.22em', textTransform: 'uppercase', marginBottom: 18 }}>studio</div>
<p style={{ fontSize: 12, lineHeight: 1.85, wordBreak: 'keep-all', maxWidth: 220, color: 'rgba(244,242,239,.4)' }}>
<br/> .
</p>
</div>
{[
{ title: 'Shop', links: ['신상품', '아우터', '상의', '하의', '액세서리', '세일'] },
{ title: 'Brand', links: ['브랜드 스토리', '지속가능성', '룩북', '장인 협업'] },
{ title: 'Support', links: ['배송·반품', '사이즈 가이드', 'FAQ', '문의하기'] },
].map(col => (
<div key={col.title}>
<div style={{ fontSize: 9, color: 'rgba(244,242,239,.4)', letterSpacing: '.2em', textTransform: 'uppercase', marginBottom: 20, fontWeight: 700 }}>{col.title}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{col.links.map(l => (
<a key={l} href="#" style={{ fontSize: 12, color: T.stone, textDecoration: 'none', transition: 'color .2s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = T.chalk)}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = T.stone)}>
{l}
</a>
))}
</div>
</div>
))}
</div>
<div style={{ borderTop: '1px solid rgba(244,242,239,.06)', paddingTop: 28, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 14 }}>
<p style={{ fontSize: 11, color: 'rgba(244,242,239,.3)', letterSpacing: '.03em' }}>© 2025 MELLOW STUDIO. All rights reserved.</p>
<div style={{ display: 'flex', gap: 24 }}>
{['개인정보처리방침', '이용약관', 'Instagram', 'Pinterest'].map(l => (
<a key={l} href="#" style={{ fontSize: 11, color: T.stone, textDecoration: 'none', transition: 'color .2s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = T.chalk)}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = T.stone)}>
{l}
</a>
))}
</div>
</div>
</div>
</footer>
{/* ════════════════════════════════════════════
QUICK-ADD HOVER — global CSS patch
════════════════════════════════════════════ */}
<style dangerouslySetInnerHTML={{ __html: `.ml-card:hover .ml-quick { opacity: 1 !important; }` }} />
{/* ════════════════════════════════════════════
SCROLL TO TOP
════════════════════════════════════════════ */}
<button onClick={scrollTop} aria-label="맨 위로"
style={{
position: 'fixed', bottom: '5.5rem', right: '2rem', zIndex: 200,
width: 46, height: 46,
background: T.ink, border: 'none', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: '0 8px 28px rgba(12,11,9,.25)',
opacity: showTop ? 1 : 0,
transform: showTop ? 'translateY(0)' : 'translateY(12px)',
transition: 'opacity .35s cubic-bezier(.16,1,.3,1), transform .35s cubic-bezier(.16,1,.3,1)',
pointerEvents: showTop ? 'auto' : 'none',
}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={T.white} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="18 15 12 9 6 15"/>
</svg>
</button>
</div>
);
}

View File

@@ -1,543 +0,0 @@
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
type Mode = 'simple' | 'custom';
type SunoClip = {
id: string;
title?: string;
audioUrl?: string;
streamAudioUrl?: string;
imageUrl?: string;
tags?: string;
duration?: number;
prompt?: string;
};
type TaskState = {
taskId: string;
status: string;
errorMessage?: string;
clips: SunoClip[];
updatedAt: number;
};
const MODELS = [
{ id: 'V4', label: 'V4 (기본)', desc: '안정적 고품질' },
{ id: 'V4_5', label: 'V4.5', desc: '최신 · 풍부한 디테일' },
{ id: 'V3_5', label: 'V3.5', desc: '빠른 생성' },
];
const TAG_PRESETS = [
'k-pop', 'lo-fi', 'city pop', 'ballad', 'edm', 'trap',
'rock', 'jazz', 'acoustic', 'cinematic', 'synthwave', 'ambient',
];
const LS_KEY = 'jsm_studio_task_ids_v2';
const isDone = (s: string) => s === 'SUCCESS' || s === 'FIRST_SUCCESS';
const isFailed = (s: string) => s.includes('FAILED') || s === 'SENSITIVE_WORD_ERROR';
export default function StudioPage() {
const [mode, setMode] = useState<Mode>('simple');
const [model, setModel] = useState('V4');
const [prompt, setPrompt] = useState('');
const [title, setTitle] = useState('');
const [lyrics, setLyrics] = useState('');
const [tags, setTags] = useState('');
const [instrumental, setInstrumental] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [tasks, setTasks] = useState<TaskState[]>([]);
const pollRef = useRef<number | null>(null);
const saveToLS = useCallback((ids: string[]) => {
if (typeof window === 'undefined') return;
try { localStorage.setItem(LS_KEY, JSON.stringify(ids.slice(0, 20))); } catch { /* noop */ }
}, []);
const fetchOne = useCallback(async (taskId: string) => {
try {
const res = await fetch(`/api/studio/status?taskId=${encodeURIComponent(taskId)}`);
const json = await res.json();
if (!json.ok) return null;
const d = json.data?.data ?? json.data;
const status: string = d?.status ?? 'PENDING';
const errMsg: string | undefined = d?.errorMessage;
const sunoData: SunoClip[] = d?.response?.sunoData ?? [];
return { taskId, status, errorMessage: errMsg, clips: sunoData, updatedAt: Date.now() } as TaskState;
} catch {
return null;
}
}, []);
const refreshAll = useCallback(async (ids: string[]) => {
const results = await Promise.all(ids.map((id) => fetchOne(id)));
setTasks((prev) => {
const map = new Map(prev.map((t) => [t.taskId, t]));
for (const r of results) if (r) map.set(r.taskId, r);
return Array.from(map.values()).sort((a, b) => b.updatedAt - a.updatedAt);
});
}, [fetchOne]);
useEffect(() => {
if (typeof window === 'undefined') return;
try {
const raw = localStorage.getItem(LS_KEY);
const ids = raw ? (JSON.parse(raw) as string[]) : [];
if (ids.length) {
setTasks(ids.map((id) => ({ taskId: id, status: 'PENDING', clips: [], updatedAt: Date.now() })));
refreshAll(ids);
}
} catch { /* noop */ }
}, [refreshAll]);
useEffect(() => {
if (pollRef.current) window.clearInterval(pollRef.current);
const pending = tasks.filter((t) => !isDone(t.status) && !isFailed(t.status));
if (pending.length) {
pollRef.current = window.setInterval(() => {
refreshAll(pending.map((t) => t.taskId));
}, 8000);
}
return () => { if (pollRef.current) window.clearInterval(pollRef.current); };
}, [tasks, refreshAll]);
const onSubmit = async () => {
setError(null);
if (mode === 'simple' && !prompt.trim()) { setError('프롬프트를 입력해주세요.'); return; }
if (mode === 'custom') {
if (!title.trim()) { setError('트랙 제목을 입력해주세요.'); return; }
if (!tags.trim()) { setError('스타일 태그를 입력해주세요.'); return; }
if (!lyrics.trim() && !instrumental) { setError('가사를 입력하거나 Instrumental을 켜주세요.'); return; }
}
setSubmitting(true);
try {
const res = await fetch('/api/studio/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mode, model,
prompt: prompt.trim(),
title: title.trim(),
lyrics: lyrics.trim(),
tags: tags.trim(),
make_instrumental: instrumental,
}),
});
const json = await res.json();
if (!res.ok || !json.ok) {
setError(typeof json.error === 'string' ? json.error : '생성 실패');
return;
}
const taskId: string | undefined = json.data?.data?.taskId ?? json.data?.taskId;
if (!taskId) {
setError('응답에서 taskId를 찾지 못했습니다.');
return;
}
setTasks((prev) => {
const next: TaskState[] = [
{ taskId, status: 'PENDING', clips: [], updatedAt: Date.now() },
...prev.filter((t) => t.taskId !== taskId),
];
saveToLS(next.map((t) => t.taskId));
return next;
});
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setSubmitting(false);
}
};
const addTag = (t: string) => {
const cur = tags.split(',').map((x) => x.trim()).filter(Boolean);
if (cur.includes(t)) return;
setTags([...cur, t].join(', '));
};
return (
<div
className="min-h-screen px-4 md:px-8 lg:px-12 py-10"
style={{
background:
'radial-gradient(1200px 600px at 20% -10%, rgba(156,72,234,0.18), transparent 60%), radial-gradient(1000px 500px at 110% 10%, rgba(83,221,252,0.12), transparent 55%), var(--kx-surface)',
color: 'var(--kx-on-surface)',
}}
>
<div className="max-w-7xl mx-auto">
<div className="flex items-end justify-between flex-wrap gap-4 mb-8">
<div>
<span className="kx-label">JAENGSEUNG STUDIO</span>
<h1 className="kx-display text-3xl md:text-5xl font-extrabold mt-2" style={{ letterSpacing: '-0.02em' }}>
</h1>
<p className="mt-2 text-sm" style={{ color: 'var(--kx-on-variant)' }}>
Suno · Custom ··
</p>
</div>
<div
className="text-xs px-3 py-1.5 rounded-full border"
style={{
borderColor: 'rgba(204,151,255,0.35)',
background: 'rgba(204,151,255,0.1)',
color: 'var(--kx-primary)',
}}
>
v1 Studio · Live
</div>
</div>
<div className="grid lg:grid-cols-[minmax(0,7fr)_minmax(0,5fr)] gap-6">
{/* 좌측: 제어판 */}
<div
className="rounded-2xl p-6 md:p-8"
style={{
background: 'rgba(12,22,45,0.7)',
border: '1px solid rgba(255,255,255,0.06)',
backdropFilter: 'blur(16px)',
}}
>
<div className="flex gap-1 p-1 rounded-full mb-6" style={{ background: 'rgba(255,255,255,0.04)' }}>
{(['simple', 'custom'] as Mode[]).map((m) => (
<button
key={m}
onClick={() => setMode(m)}
className="flex-1 py-2.5 text-sm font-semibold rounded-full transition-all"
style={
mode === m
? {
background: 'linear-gradient(135deg, rgba(204,151,255,0.25), rgba(83,221,252,0.15))',
color: '#fff',
boxShadow: '0 0 24px rgba(204,151,255,0.25) inset',
}
: { color: 'var(--kx-on-variant)' }
}
>
{m === 'simple' ? '간단 모드' : 'Custom 모드'}
</button>
))}
</div>
{mode === 'simple' ? (
<div className="space-y-5">
<Field label="프롬프트" hint="무드·장르·가사 아이디어를 한 줄로">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
rows={5}
placeholder="예: 비 오는 서울 새벽, 감성 시티팝 with 여성 보컬, 2010년대 무드"
className="w-full bg-transparent outline-none resize-none text-base"
style={{ color: 'var(--kx-on-surface)' }}
/>
</Field>
</div>
) : (
<div className="space-y-5">
<Field label="트랙 제목">
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="예: 새벽 세 시의 도시"
className="w-full bg-transparent outline-none text-base"
style={{ color: 'var(--kx-on-surface)' }}
/>
</Field>
<Field label="가사" hint="Suno 포맷: [Verse] [Chorus] [Bridge] 등 태그 가능">
<textarea
value={lyrics}
onChange={(e) => setLyrics(e.target.value)}
rows={8}
placeholder={'[Verse]\n차가운 조명 아래 걷는 나\n새벽 세 시의 도시는 낯설어\n\n[Chorus]\n...'}
className="w-full bg-transparent outline-none resize-none font-mono text-sm leading-relaxed"
style={{ color: 'var(--kx-on-surface)' }}
/>
</Field>
<Field label="스타일 태그" hint="쉼표로 구분 · 장르·무드·악기·보컬 톤">
<input
value={tags}
onChange={(e) => setTags(e.target.value)}
placeholder="city pop, female vocal, 120bpm, synth, nostalgic"
className="w-full bg-transparent outline-none text-base"
style={{ color: 'var(--kx-on-surface)' }}
/>
<div className="flex flex-wrap gap-1.5 mt-3">
{TAG_PRESETS.map((t) => (
<button
key={t}
onClick={() => addTag(t)}
className="text-xs px-2.5 py-1 rounded-full transition"
style={{
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.08)',
color: 'var(--kx-on-variant)',
}}
>
+ {t}
</button>
))}
</div>
</Field>
</div>
)}
<div className="grid grid-cols-2 gap-4 mt-6">
<Field label="모델">
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="w-full bg-transparent outline-none text-sm"
style={{ color: 'var(--kx-on-surface)' }}
>
{MODELS.map((m) => (
<option key={m.id} value={m.id} style={{ background: '#0b1428' }}>
{m.label} {m.desc}
</option>
))}
</select>
</Field>
<Field label="Instrumental (가사 없음)">
<label className="flex items-center gap-3 cursor-pointer">
<span
className="relative inline-block w-11 h-6 rounded-full transition"
style={{ background: instrumental ? 'rgba(204,151,255,0.6)' : 'rgba(255,255,255,0.1)' }}
>
<span
className="absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all"
style={{ left: instrumental ? '22px' : '2px' }}
/>
</span>
<input
type="checkbox"
checked={instrumental}
onChange={(e) => setInstrumental(e.target.checked)}
className="sr-only"
/>
<span className="text-xs" style={{ color: 'var(--kx-on-variant)' }}>
{instrumental ? 'ON' : 'OFF'}
</span>
</label>
</Field>
</div>
<div className="mt-8">
<button
onClick={onSubmit}
disabled={submitting}
className="w-full py-4 rounded-xl font-extrabold text-base transition-all disabled:opacity-60"
style={{
background: submitting
? 'rgba(204,151,255,0.2)'
: 'linear-gradient(135deg, #cc97ff 0%, #7c3aed 50%, #53ddfc 100%)',
color: '#0b1428',
boxShadow: submitting ? 'none' : '0 12px 40px -12px rgba(204,151,255,0.6)',
letterSpacing: '0.01em',
}}
>
{submitting ? '생성 요청 중…' : '▶ Generate Track'}
</button>
{error && (
<p className="mt-3 text-xs px-3 py-2 rounded-lg" style={{ background: 'rgba(215,51,87,0.12)', color: '#ff8ba7' }}>
{error}
</p>
)}
<p className="mt-3 text-[11px] leading-relaxed" style={{ color: 'var(--kx-on-variant)' }}>
Suno . · .
</p>
</div>
</div>
{/* 우측: 결과 */}
<div
className="rounded-2xl p-6 md:p-7"
style={{
background: 'rgba(9,17,36,0.7)',
border: '1px solid rgba(255,255,255,0.06)',
backdropFilter: 'blur(16px)',
}}
>
<div className="flex items-center justify-between mb-4">
<div>
<span className="kx-label">RECENT TRACKS</span>
<h2 className="kx-display text-xl font-bold mt-1"> </h2>
</div>
{tasks.length > 0 && (
<button
onClick={() => { setTasks([]); saveToLS([]); }}
className="text-[11px] underline underline-offset-4"
style={{ color: 'var(--kx-on-variant)' }}
>
</button>
)}
</div>
{tasks.length === 0 ? (
<div
className="rounded-xl p-8 text-center text-sm"
style={{ border: '1px dashed rgba(255,255,255,0.1)', color: 'var(--kx-on-variant)' }}
>
.
<br /> Generate를 .
</div>
) : (
<ul className="space-y-4 max-h-[640px] overflow-y-auto pr-1">
{tasks.map((task) => (
<li
key={task.taskId}
className="rounded-xl p-4"
style={{
background: 'rgba(20,31,56,0.6)',
border: '1px solid rgba(255,255,255,0.05)',
}}
>
<div className="flex items-center justify-between gap-3 mb-3">
<span className="text-[11px] font-mono opacity-60">task: {task.taskId.slice(0, 10)}</span>
<StatusBadge status={task.status} />
</div>
{task.clips.length === 0 ? (
<div
className="h-9 rounded-md flex items-center justify-center text-xs"
style={{
background: 'linear-gradient(90deg, rgba(204,151,255,0.08) 0%, rgba(83,221,252,0.08) 100%)',
color: 'var(--kx-on-variant)',
}}
>
{isFailed(task.status)
? (task.errorMessage ?? '생성 실패')
: '오디오 생성 중… (보통 1~3분)'}
</div>
) : (
<div className="space-y-3">
{task.clips.map((c) => {
const src = c.audioUrl || c.streamAudioUrl;
return (
<div
key={c.id}
className="rounded-lg p-3"
style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.04)' }}
>
<div className="flex items-center gap-3">
{c.imageUrl && (
// eslint-disable-next-line @next/next/no-img-element
<img
src={c.imageUrl}
alt=""
className="w-12 h-12 rounded-md object-cover flex-shrink-0"
/>
)}
<div className="min-w-0 flex-1">
<p className="font-semibold text-sm truncate" style={{ color: 'var(--kx-on-surface)' }}>
{c.title || '제목 없음'}
</p>
{c.tags && (
<p className="text-[11px] truncate mt-0.5" style={{ color: 'var(--kx-on-variant)' }}>
{c.tags}
</p>
)}
</div>
{c.duration && (
<span className="text-[10px] font-mono opacity-60">
{Math.round(c.duration)}s
</span>
)}
</div>
{src ? (
<audio controls src={src} className="w-full mt-2" style={{ height: 36 }} />
) : null}
{c.audioUrl && (
<div className="mt-1.5 text-[11px]" style={{ color: 'var(--kx-on-variant)' }}>
<a href={c.audioUrl} download className="underline underline-offset-4 hover:text-white">
MP3
</a>
</div>
)}
</div>
);
})}
</div>
)}
</li>
))}
</ul>
)}
</div>
</div>
<div className="mt-10 grid md:grid-cols-3 gap-4 text-xs" style={{ color: 'var(--kx-on-variant)' }}>
<Tip title="① 간단 모드" body="한 줄 프롬프트로 즉시 생성. 결과물 다양성 높음." />
<Tip title="② Custom 모드" body="가사·태그·보컬·악기까지 정밀 제어. 반복 생성에 유리." />
<Tip title="③ 상업 이용" body="Suno Pro 이상 플랜에서 생성한 결과만 수익화 가능. 플랜 확인 필수." />
</div>
</div>
</div>
);
}
function Field({
label,
hint,
children,
}: {
label: string;
hint?: string;
children: React.ReactNode;
}) {
return (
<div
className="rounded-xl p-4"
style={{
background: 'rgba(255,255,255,0.02)',
border: '1px solid rgba(255,255,255,0.06)',
}}
>
<div className="flex items-baseline justify-between mb-2">
<span className="text-[11px] font-semibold tracking-widest uppercase" style={{ color: 'var(--kx-primary)' }}>
{label}
</span>
{hint && <span className="text-[10px]" style={{ color: 'var(--kx-on-variant)' }}>{hint}</span>}
</div>
{children}
</div>
);
}
function StatusBadge({ status }: { status: string }) {
const map: Record<string, { bg: string; fg: string; label: string }> = {
SUCCESS: { bg: 'rgba(64,206,172,0.18)', fg: '#6cf0c6', label: '완료' },
FIRST_SUCCESS: { bg: 'rgba(83,221,252,0.18)', fg: '#53ddfc', label: '첫 트랙 준비' },
TEXT_SUCCESS: { bg: 'rgba(83,221,252,0.18)', fg: '#53ddfc', label: '가사 완료' },
PENDING: { bg: 'rgba(204,151,255,0.18)', fg: '#cc97ff', label: '대기' },
};
let entry = map[status];
if (!entry) {
entry = isFailed(status)
? { bg: 'rgba(215,51,87,0.18)', fg: '#ff8ba7', label: '실패' }
: { bg: 'rgba(255,255,255,0.06)', fg: 'rgba(255,255,255,0.6)', label: status };
}
return (
<span
className="text-[10px] font-semibold px-2 py-0.5 rounded-full whitespace-nowrap"
style={{ background: entry.bg, color: entry.fg }}
>
{entry.label}
</span>
);
}
function Tip({ title, body }: { title: string; body: string }) {
return (
<div
className="rounded-xl p-4"
style={{ background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}
>
<p className="font-semibold mb-1" style={{ color: 'var(--kx-on-surface)' }}>
{title}
</p>
<p className="leading-relaxed">{body}</p>
</div>
);
}