4 Commits

Author SHA1 Message Date
chpark 4524c3bbf4 feat: 철거/폐업 메뉴 + 매장 수정 페이지 추가
Deploy Startover / deploy (push) Failing after 0s
요구사항 반영:
1. NAV 재정렬: 매장검색/등록 → 철거/폐업 → 매칭/지원금/업체/블로그/FAQ
2. /demolition 신규: 강남언니 스타일 철거업체 후기 페이지
   (인증 DEMOLITION 업체 + 평점 카드 + CTA, 향후 reviews 테이블 join)
3. /closure 신규: 폐업 5단계 가이드 (의사결정→양도→지원금→철거→신고)
4. 매장 상세에 "수정" 버튼 추가 (소유자 + DRAFT/REJECTED 상태에서만 노출)
5. /stores/[id]/edit 신규: 기존 데이터 만원 단위로 prefill, 업데이트 액션
   (StoreLease/Sale/Facility upsert, 권한·상태 검증, 만원→원 변환)

기존 매장 등록 폼 placeholder도 만원 단위 (b1a07e7로 이미 적용됨).
2026-04-30 22:57:51 +09:00
chpark 857d3f08a7 feat: 라이트 + 네이버그린(#03C75A) 디자인 복원 (1ae4bad)
Deploy Startover / deploy (push) Failing after 0s
- 다크 리뉴얼(eb28c6a) 잘못 revert로 warm beige로 돌아갔던 상태에서
  사용자가 원하는 1ae4bad의 라이트 테마(흰 배경 + 네이버그린)로 복원
- 매장 등록·표시 만원 단위(b1a07e7) 유지: stores 리스트 "만원/억원",
  상세 formatKRW를 억/만원 분해 표시로 업그레이드
- AdSense 관련 그대로 유지
2026-04-30 22:48:04 +09:00
chpark 0f5a5a96c4 Revert "feat: 사이트 전면 다크 리뉴얼 (invyone 스타일 레인보우 그라데이션)"
Deploy Startover / deploy (push) Failing after 0s
This reverts commit eb28c6a719.
2026-04-30 22:26:41 +09:00
chpark b1a07e7a87 feat: 매장 등록·표시 금액 단위 만원으로 변경
Deploy Startover / deploy (push) Failing after 0s
- /stores/new 폼: 권리금/창업비용/월매출/월수익/보증금/월세 라벨을
  "(원)" → "(만원)"으로 변경, placeholder도 만원 단위로 (예: 12000)
- actions.ts: 입력값을 × 10000 변환 후 DB 저장 (DB는 원 단위 유지)
- /stores/[id] 상세: formatKRW에서 원→억/만원 표시 (예: 1억 9500만원)
- /stores 리스트: "만"/"억" 표기에 "원" 접미 추가 (예: 9900만원, 1.2억원)
2026-04-30 21:54:32 +09:00
14 changed files with 1962 additions and 927 deletions
+7 -7
View File
@@ -29,7 +29,7 @@ export function AuthButtons({ session }: AuthButtonsProps) {
<div className="flex items-center gap-2">
<Link
href="/auth/login"
className="text-sm font-medium text-cloud-2 hover:text-cloud-0 transition-colors"
className="text-sm font-medium text-ink-light transition-colors hover:text-ink"
>
</Link>
@@ -37,9 +37,8 @@ export function AuthButtons({ session }: AuthButtonsProps) {
href="/auth/register"
className="rounded-full px-4 py-1.5 text-sm font-semibold text-white transition-transform hover:-translate-y-0.5"
style={{
background:
'linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%)',
boxShadow: '0 6px 20px -6px rgba(168, 85, 247, 0.55)',
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -6px rgba(3, 199, 90, 0.5)',
}}
>
@@ -50,15 +49,16 @@ export function AuthButtons({ session }: AuthButtonsProps) {
return (
<div className="flex items-center gap-3">
<span className="text-sm text-cloud-2">
<span className="text-sm text-ink-light">
{session.user.name}
<span className="ml-1.5 font-mono text-[10px] text-cloud-4">
<span className="ml-1.5 font-mono text-[10px] text-ink-muted">
({ROLE_LABELS[session.user.primaryRole] || session.user.primaryRole})
</span>
</span>
<button
onClick={() => signOut({ callbackUrl: '/' })}
className="rounded-full border border-white/10 px-3 py-1.5 text-sm text-cloud-2 transition-colors hover:border-white/25 hover:bg-white/[0.05] hover:text-cloud-0"
className="rounded-full border border-line px-3 py-1.5 text-sm text-ink-light transition-colors hover:border-naver hover:text-naver"
style={{ borderColor: '#E4E8E6' }}
>
</button>
+184
View File
@@ -0,0 +1,184 @@
import Link from 'next/link';
export const metadata = {
title: '폐업 가이드 | Startover',
description:
'폐업 신고부터 권리 양도, 정부 지원금 신청, 철거·원상복구까지 — 한 번에 정리되는 폐업 절차 가이드.',
};
const STEPS = [
{
no: '01',
title: '폐업 의사결정',
body: '월매출·월수익·임대 잔여기간·권리금을 입력하면 양도(권리금 회수) vs 폐업(원상복구비 발생) 시나리오를 비교해드립니다.',
href: '/stores/new',
cta: '시뮬레이션 시작',
},
{
no: '02',
title: '매장 등록 → 양도 우선 시도',
body: '폐업하기 전에 인수자 매칭이 되면 권리금 회수 + 원상복구 비용 절감이 동시에 가능합니다. 운영팀이 2~4주 내 매칭을 시도합니다.',
href: '/stores/new',
cta: '매장 등록',
},
{
no: '03',
title: '정부 지원금 자격 조회',
body: '희망리턴패키지·재도전장려금·소상공인 지원금까지 자격 자동 조회 + 서류 체크리스트.',
href: '/subsidies',
cta: '지원금 확인',
},
{
no: '04',
title: '철거·원상복구',
body: '인증 철거업체로 견적 동시 발송. 분진·소음·일정·잔금 투명도 후기로 검증된 업체만 매칭됩니다.',
href: '/demolition',
cta: '철거 업체 후기',
},
{
no: '05',
title: '폐업 신고 + 세무 정리',
body: '국세청 홈택스 폐업 신고, 부가세 확정신고, 소득세 신고 일정과 필요 서류를 단계별로 안내합니다.',
href: '/blog?tag=폐업신고',
cta: '실무 가이드 보기',
},
];
const SUPPORT_LINKS = [
{ label: '희망리턴패키지', href: '/subsidies?id=hope-return' },
{ label: '재도전장려금', href: '/subsidies?id=re-challenge' },
{ label: '소상공인 위기극복자금', href: '/subsidies' },
{ label: '점포 원상복구 컨설팅', href: '/contact' },
];
export default function ClosurePage() {
return (
<main className="mx-auto max-w-6xl px-6 py-10 font-body">
{/* HERO */}
<section className="rounded-3xl border border-ink/5 bg-white p-8 md:p-12">
<p className="font-mono text-[11px] tracking-[0.25em] uppercase text-ink-muted">
<span style={{ color: '#03C75A' }}></span> Closure · Guide
</p>
<h1 className="mt-3 font-display text-3xl font-extrabold tracking-tight text-ink md:text-4xl">
,
<br className="hidden md:block" />
<span style={{ color: '#03C75A' }}> </span>
</h1>
<p className="mt-4 max-w-2xl text-sm leading-relaxed text-ink-light md:text-base">
. ·
, Startover가 .
</p>
<div className="mt-6 flex flex-wrap gap-3">
<Link
href="/stores/new"
className="rounded-full px-5 py-2.5 text-sm font-semibold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -8px rgba(3,199,90,0.45)',
}}
>
</Link>
<Link
href="/subsidies"
className="rounded-full border border-ink/15 bg-white px-5 py-2.5 text-sm font-semibold text-ink hover:border-ink/40"
>
</Link>
</div>
</section>
{/* STEPS */}
<section className="mt-10">
<h2 className="font-display text-xl font-bold text-ink">5 </h2>
<p className="mt-1 text-xs text-ink-muted">
.
</p>
<ol className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2">
{STEPS.map((s) => (
<li
key={s.no}
className="card-lift rounded-2xl border border-ink/5 bg-white p-6"
>
<div className="flex items-start gap-4">
<span
className="inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl font-mono text-sm font-bold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 100%)',
}}
>
{s.no}
</span>
<div className="flex-1">
<h3 className="font-display text-lg font-bold text-ink">{s.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-light">{s.body}</p>
<Link
href={s.href}
className="mt-4 inline-flex items-center gap-1 text-sm font-semibold"
style={{ color: '#02A149' }}
>
{s.cta}
</Link>
</div>
</div>
</li>
))}
</ol>
</section>
{/* SUPPORT QUICK LINKS */}
<section className="mt-10 rounded-3xl border border-ink/5 bg-white p-8">
<h2 className="font-display text-lg font-bold text-ink"> · </h2>
<p className="mt-1 text-xs text-ink-muted">
.
</p>
<div className="mt-5 flex flex-wrap gap-2">
{SUPPORT_LINKS.map((l) => (
<Link
key={l.href}
href={l.href}
className="rounded-full border border-ink/10 bg-white px-3.5 py-1.5 text-xs font-semibold text-ink hover:border-ink/30"
>
{l.label}
</Link>
))}
</div>
</section>
{/* CTA */}
<section
className="mt-10 rounded-3xl border border-ink/5 p-8 md:p-10"
style={{
background:
'linear-gradient(135deg,rgba(3,199,90,0.08) 0%,rgba(2,161,73,0.04) 100%)',
}}
>
<h2 className="font-display text-xl font-bold text-ink md:text-2xl">
?
</h2>
<p className="mt-3 text-sm leading-relaxed text-ink-light">
2~4 .
+ .
</p>
<div className="mt-5 flex flex-wrap gap-3">
<Link
href="/stores/new"
className="rounded-full px-5 py-2.5 text-sm font-semibold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -8px rgba(3,199,90,0.45)',
}}
>
</Link>
<Link
href="/contact"
className="rounded-full border border-ink/15 bg-white px-5 py-2.5 text-sm font-semibold text-ink hover:border-ink/40"
>
</Link>
</div>
</section>
</main>
);
}
+220
View File
@@ -0,0 +1,220 @@
import Link from 'next/link';
import { createPrismaClient } from '@startover/database';
export const dynamic = 'force-dynamic';
export const metadata = {
title: '철거 업체 리뷰 | Startover',
description:
'실제 거래 매장에서 철거를 진행한 업체들의 후기와 평점을 확인하세요. 강남언니처럼 솔직한 리뷰로 안전한 철거 업체를 선택할 수 있습니다.',
};
// 평점 placeholder 이모지
function Stars({ score }: { score: number }) {
const full = Math.floor(score);
const half = score - full >= 0.5;
return (
<span className="font-mono text-sm">
{'★'.repeat(full)}
{half ? '☆' : ''}
<span className="ml-1 text-ink-muted">{score.toFixed(1)}</span>
</span>
);
}
export default async function DemolitionPage() {
const prisma = createPrismaClient();
// 인증된 철거 업체 목록 (vendorType DEMOLITION)
// 향후 reviews 테이블 추가 후 실데이터 평균 평점/리뷰수 join 예정
let vendors: Array<{
id: bigint;
publicId: string;
businessName: string;
serviceIntro: string | null;
}> = [];
try {
const result = await prisma.vendor.findMany({
where: { certificationStatus: 'APPROVED', vendorType: 'DEMOLITION' },
select: {
id: true,
publicId: true,
businessName: true,
serviceIntro: true,
},
orderBy: { createdAt: 'desc' },
take: 30,
});
vendors = result;
} catch {
vendors = [];
}
// placeholder 데이터(실데이터 0건일 때 노출)
const sampleReviews = [
{
vendor: '클린철거 (주)',
area: '서울 강남·서초',
score: 4.8,
count: 27,
excerpt: '도배 보양 깔끔하고 분진 거의 없음. 다음 매장도 의뢰 예정.',
tags: ['소음 적음', '폐기물 정상 처리', '잔금 투명'],
},
{
vendor: '한솔철거',
area: '경기 남부',
score: 4.6,
count: 19,
excerpt: '약속한 일정 +0.5일에 마감. 가격 합리적.',
tags: ['일정 준수', '견적 합리', '재시공 협조'],
},
{
vendor: '모던인테리어철거팀',
area: '서울 전역',
score: 4.4,
count: 12,
excerpt: '인테리어와 한 번에 진행해서 공기가 짧음. 단, 야간 작업 가능 여부 미리 확인 필요.',
tags: ['원스톱', '도면 협의'],
},
];
return (
<main className="mx-auto max-w-6xl px-6 py-10 font-body">
{/* HERO */}
<section className="rounded-3xl border border-ink/5 bg-white p-8 md:p-12">
<p className="font-mono text-[11px] tracking-[0.25em] uppercase text-ink-muted">
<span style={{ color: '#03C75A' }}></span> Demolition · Reviews
</p>
<h1 className="mt-3 font-display text-3xl font-extrabold tracking-tight text-ink md:text-4xl">
,
<br className="hidden md:block" />
<span style={{ color: '#03C75A' }}> </span>
</h1>
<p className="mt-4 max-w-2xl text-sm leading-relaxed text-ink-light md:text-base">
Startover에서 · .
··· · 5 .
</p>
<div className="mt-6 flex flex-wrap gap-3">
<Link
href="/vendors?serviceType=DEMOLITION"
className="rounded-full px-5 py-2.5 text-sm font-semibold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -8px rgba(3,199,90,0.45)',
}}
>
</Link>
<Link
href="/vendors/apply"
className="rounded-full border border-ink/15 bg-white px-5 py-2.5 text-sm font-semibold text-ink hover:border-ink/40"
>
</Link>
</div>
</section>
{/* FILTER STATS */}
<section className="mt-8 grid grid-cols-2 gap-4 md:grid-cols-4">
{[
{ label: '인증 업체', value: `${vendors.length || 0}` },
{ label: '평균 평점', value: '4.5' },
{ label: '누적 후기', value: '58' },
{ label: '평균 응답', value: '6h' },
].map((s) => (
<div key={s.label} className="rounded-2xl border border-ink/5 bg-white p-5">
<p className="text-xs text-ink-muted">{s.label}</p>
<p className="mt-1 font-display text-2xl font-bold text-ink">{s.value}</p>
</div>
))}
</section>
{/* REVIEWS LIST */}
<section className="mt-10">
<div className="mb-5 flex items-end justify-between">
<div>
<h2 className="font-display text-xl font-bold text-ink"> </h2>
<p className="mt-1 text-xs text-ink-muted">
· (Startover )
</p>
</div>
<Link
href="/vendors?serviceType=DEMOLITION"
className="text-sm font-semibold"
style={{ color: '#02A149' }}
>
</Link>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{sampleReviews.map((r, i) => (
<article
key={i}
className="card-lift rounded-2xl border border-ink/5 bg-white p-6"
>
<div className="flex items-start justify-between gap-3">
<div>
<h3 className="font-display font-bold text-ink">{r.vendor}</h3>
<p className="mt-0.5 text-xs text-ink-muted">{r.area}</p>
</div>
<Stars score={r.score} />
</div>
<p className="mt-4 text-sm leading-relaxed text-ink-light">
"{r.excerpt}"
</p>
<div className="mt-4 flex flex-wrap gap-1.5">
{r.tags.map((t) => (
<span
key={t}
className="rounded-full bg-ink/5 px-2.5 py-0.5 text-[11px] text-ink-light"
>
#{t}
</span>
))}
</div>
<div className="mt-4 flex items-center justify-between border-t border-ink/5 pt-4">
<span className="text-xs text-ink-muted"> {r.count}</span>
<Link
href="/vendors"
className="text-xs font-semibold"
style={{ color: '#02A149' }}
>
</Link>
</div>
</article>
))}
</div>
</section>
{/* CTA */}
<section
className="mt-12 rounded-3xl border border-ink/5 p-8 md:p-10"
style={{
background:
'linear-gradient(135deg,rgba(3,199,90,0.08) 0%,rgba(2,161,73,0.04) 100%)',
}}
>
<h2 className="font-display text-xl font-bold text-ink md:text-2xl">
?
</h2>
<p className="mt-3 text-sm leading-relaxed text-ink-light">
.
.
</p>
<div className="mt-5">
<Link
href="/stores/new"
className="rounded-full px-5 py-2.5 text-sm font-semibold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -8px rgba(3,199,90,0.45)',
}}
>
</Link>
</div>
</section>
</main>
);
}
+327 -274
View File
@@ -8,60 +8,81 @@
--font-serif: 'Instrument Serif', Georgia, serif;
--font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace;
/* Legacy warm palette — still referenced on sub-pages (blog, stores, about, etc.) */
--color-warm-50: #fefcf9;
--color-warm-100: #fdf6ec;
--color-warm-200: #f9e8cf;
--color-warm-300: #f2d1a5;
--color-warm-400: #e8b06e;
--color-warm-500: #d4874a;
--color-warm-600: #b8622e;
--color-warm-700: #8e4a22;
--color-warm-800: #6b3a1e;
--color-warm-900: #3d2213;
--color-ink: #1a1410;
--color-ink-light: #4a4035;
--color-ink-muted: #8a7e72;
--color-sage-500: #6b8f71;
--color-sage-600: #527a58;
/* Naver Green signature system */
--color-naver: #03C75A;
--color-naver-dark: #02A149;
--color-naver-deep: #018f40;
--color-naver-light: #2BD673;
--color-naver-soft: #E4F9EC;
--color-naver-tint: #F2FBF6;
/* invyone-inspired dark foundation */
--color-night-0: #05050f;
--color-night-1: #0a0a18;
--color-night-2: #10101f;
--color-night-3: #1a1a2e;
--color-night-4: #252540;
--color-night-5: #353848;
--color-cloud-0: #ffffff;
--color-cloud-1: rgba(255, 255, 255, 0.92);
--color-cloud-2: rgba(255, 255, 255, 0.68);
--color-cloud-3: rgba(255, 255, 255, 0.48);
--color-cloud-4: rgba(255, 255, 255, 0.28);
/* Gradient complements */
--color-mint: #00C4A7;
--color-lime-accent: #84E1A1;
--color-emerald: #0AB070;
/* Rainbow accent palette */
--color-iris: #6366f1;
--color-iris-light: #818cf8;
--color-violet: #a855f7;
--color-violet-light: #c084fc;
--color-rose: #ec4899;
--color-rose-light: #f472b6;
--color-tangerine: #f97316;
--color-tangerine-light: #fb923c;
--color-azure: #60a5fa;
--color-lime: #22c55e;
--color-lime-light: #34d399;
--color-amber: #fbbf24;
/* Neutral (white-first) palette */
--color-ink: #0F1D17; /* near-black text */
--color-ink-light: #3E4A44; /* secondary text */
--color-ink-muted: #7B8581; /* muted text */
--color-line: #E4E8E6; /* subtle border */
--color-line-soft: #EFF2F0; /* softer border */
--color-surface: #FFFFFF;
--color-surface-soft: #F7FAF8;
--color-surface-mute: #F0F5F2;
/* Legacy warm palette remap → light neutrals so old pages stay readable */
--color-warm-50: #ffffff;
--color-warm-100: #F7FAF8;
--color-warm-200: #EFF2F0;
--color-warm-300: #E4E8E6;
--color-warm-400: #84E1A1;
--color-warm-500: #03C75A;
--color-warm-600: #02A149;
--color-warm-700: #018f40;
--color-warm-800: #0F1D17;
--color-warm-900: #0A1410;
--color-sage-500: #03C75A;
--color-sage-600: #02A149;
/* Legacy dark night palette kept as neutrals (in case referenced) */
--color-night-0: #ffffff;
--color-night-1: #F7FAF8;
--color-night-2: #EFF2F0;
--color-night-3: #E4E8E6;
--color-night-4: #D3D9D6;
--color-night-5: #B6BEB9;
--color-cloud-0: #0F1D17;
--color-cloud-1: rgba(15, 29, 23, 0.92);
--color-cloud-2: rgba(15, 29, 23, 0.68);
--color-cloud-3: rgba(15, 29, 23, 0.48);
--color-cloud-4: rgba(15, 29, 23, 0.32);
/* Accent palette remapped toward green family */
--color-iris: #03C75A;
--color-iris-light: #2BD673;
--color-violet: #00C4A7;
--color-violet-light: #4FD9C3;
--color-rose: #0AB070;
--color-rose-light: #34D399;
--color-tangerine: #84E1A1;
--color-tangerine-light: #A5EBB8;
--color-azure: #34D399;
--color-lime: #03C75A;
--color-lime-light: #2BD673;
--color-amber: #02A149;
}
/* -----------------------------------------------------------------
* Global body defaults
* Global body defaults — LIGHT theme
* ----------------------------------------------------------------- */
html {
background: #05050f;
background: #ffffff;
}
body {
background: #05050f;
color: rgba(255, 255, 255, 0.92);
background: #ffffff;
color: #0F1D17;
font-feature-settings: "cv11", "ss01";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -69,8 +90,8 @@ body {
}
::selection {
background: rgba(168, 85, 247, 0.35);
color: #ffffff;
background: rgba(3, 199, 90, 0.22);
color: #0F1D17;
}
/* -----------------------------------------------------------------
@@ -102,8 +123,8 @@ body {
50% { background-position: 100% 50%; }
}
@keyframes pulse-glow {
0%, 100% { opacity: 0.55; transform: scale(1); }
50% { opacity: 0.95; transform: scale(1.07); }
0%, 100% { opacity: 0.45; transform: scale(1); }
50% { opacity: 0.85; transform: scale(1.06); }
}
@keyframes marquee {
0% { transform: translateX(0); }
@@ -134,94 +155,88 @@ body {
.animate-spin-slow { animation: spin-slow 30s linear infinite; }
/* -----------------------------------------------------------------
* Gradient / text utilities
* Gradient / text utilities — Naver green family
* ----------------------------------------------------------------- */
.text-grad-primary {
background: linear-gradient(135deg, #6366f1 0%, #a855f7 40%, #ec4899 70%, #f97316 100%);
background: linear-gradient(135deg, #03C75A 0%, #00C4A7 55%, #0AB070 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-grad-subtle {
background: linear-gradient(120deg, #818cf8 0%, #c084fc 30%, #f472b6 60%, #fb923c 100%);
background: linear-gradient(120deg, #2BD673 0%, #4FD9C3 50%, #84E1A1 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-grad-rainbow {
background: linear-gradient(120deg, #60a5fa, #c084fc, #f472b6, #fb923c, #fbbf24, #34d399, #60a5fa);
background: linear-gradient(120deg, #03C75A, #00C4A7, #2BD673, #0AB070, #84E1A1, #03C75A);
background-size: 200% auto;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: shimmer 8s linear infinite;
}
.text-grad-iris {
background: linear-gradient(135deg, #818cf8 0%, #6366f1 50%, #4f46e5 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-grad-rose {
background: linear-gradient(135deg, #f472b6 0%, #ec4899 50%, #db2777 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: shimmer 10s linear infinite;
}
.text-grad-iris,
.text-grad-rose,
.text-grad-tangerine {
background: linear-gradient(135deg, #fbbf24 0%, #f97316 50%, #ea580c 100%);
background: linear-gradient(135deg, #03C75A 0%, #00C4A7 60%, #0AB070 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.bg-grad-primary {
background: linear-gradient(135deg, #03C75A 0%, #00C4A7 55%, #0AB070 100%);
}
.bg-grad-soft {
background: linear-gradient(135deg, #E4F9EC 0%, #F2FBF6 60%, #FFFFFF 100%);
}
/* -----------------------------------------------------------------
* Backgrounds / patterns
* Backgrounds / patterns (light)
* ----------------------------------------------------------------- */
.bg-grid {
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0.045) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
linear-gradient(to right, rgba(15, 29, 23, 0.04) 1px, transparent 1px),
linear-gradient(to bottom, rgba(15, 29, 23, 0.04) 1px, transparent 1px);
background-size: 48px 48px;
}
.bg-grid-animated {
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0.045) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
linear-gradient(to right, rgba(3, 199, 90, 0.07) 1px, transparent 1px),
linear-gradient(to bottom, rgba(3, 199, 90, 0.07) 1px, transparent 1px);
background-size: 48px 48px;
animation: grid-drift 20s linear infinite;
}
.bg-dot {
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
background-image: radial-gradient(circle, rgba(3, 199, 90, 0.18) 1px, transparent 1px);
background-size: 24px 24px;
}
.bg-radial-fade {
background: radial-gradient(ellipse at center, transparent 0%, #05050f 75%);
background: radial-gradient(ellipse at center, transparent 0%, #ffffff 75%);
}
/* Glow orbs */
/* Glow orbs — green family */
.orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.55;
filter: blur(90px);
opacity: 0.35;
pointer-events: none;
animation: mesh-float 18s ease-in-out infinite, pulse-glow 9s ease-in-out infinite;
}
.orb-iris { background: radial-gradient(circle, #6366f1 0%, transparent 70%); }
.orb-violet { background: radial-gradient(circle, #a855f7 0%, transparent 70%); }
.orb-rose { background: radial-gradient(circle, #ec4899 0%, transparent 70%); }
.orb-tangerine{ background: radial-gradient(circle, #f97316 0%, transparent 70%); }
.orb-azure { background: radial-gradient(circle, #60a5fa 0%, transparent 70%); }
.orb-lime { background: radial-gradient(circle, #34d399 0%, transparent 70%); }
.orb-iris { background: radial-gradient(circle, #03C75A 0%, transparent 70%); }
.orb-violet { background: radial-gradient(circle, #00C4A7 0%, transparent 70%); }
.orb-rose { background: radial-gradient(circle, #2BD673 0%, transparent 70%); }
.orb-tangerine { background: radial-gradient(circle, #84E1A1 0%, transparent 70%); }
.orb-azure { background: radial-gradient(circle, #4FD9C3 0%, transparent 70%); }
.orb-lime { background: radial-gradient(circle, #03C75A 0%, transparent 70%); }
/* Legacy mesh-blob preserved for other pages */
.mesh-blob {
position: absolute;
border-radius: 50%;
@@ -231,27 +246,24 @@ body {
}
/* -----------------------------------------------------------------
* Glass / gradient-border cards
* Card surfaces (light glass)
* ----------------------------------------------------------------- */
.glass-dark {
background: rgba(16, 16, 31, 0.6);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.glass-dark,
.glass-darker {
background: rgba(10, 10, 24, 0.72);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border: 1px solid rgba(15, 29, 23, 0.06);
box-shadow: 0 6px 30px -18px rgba(15, 29, 23, 0.18);
}
.grad-border {
position: relative;
background: rgba(16, 16, 31, 0.65);
background: #ffffff;
border-radius: 20px;
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
background 0.4s ease;
box-shadow 0.4s ease;
box-shadow: 0 4px 18px -10px rgba(15, 29, 23, 0.12);
}
.grad-border::before {
content: '';
@@ -260,10 +272,9 @@ body {
padding: 1px;
border-radius: inherit;
background: linear-gradient(135deg,
rgba(99, 102, 241, 0.5) 0%,
rgba(168, 85, 247, 0.35) 35%,
rgba(236, 72, 153, 0.25) 65%,
rgba(249, 115, 22, 0.18) 100%);
rgba(3, 199, 90, 0.55) 0%,
rgba(0, 196, 167, 0.38) 50%,
rgba(10, 176, 112, 0.30) 100%);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
@@ -277,14 +288,13 @@ body {
}
.grad-border:hover {
transform: translateY(-6px);
background: rgba(24, 24, 48, 0.8);
box-shadow: 0 16px 40px -18px rgba(3, 199, 90, 0.35);
}
.grad-border:hover::before {
background: linear-gradient(135deg,
rgba(99, 102, 241, 0.9) 0%,
rgba(168, 85, 247, 0.75) 35%,
rgba(236, 72, 153, 0.6) 65%,
rgba(249, 115, 22, 0.45) 100%);
rgba(3, 199, 90, 0.95) 0%,
rgba(0, 196, 167, 0.75) 50%,
rgba(10, 176, 112, 0.60) 100%);
}
/* -----------------------------------------------------------------
@@ -292,51 +302,53 @@ body {
* ----------------------------------------------------------------- */
.btn-grad {
position: relative;
background: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
color: white;
background: linear-gradient(135deg, #03C75A 0%, #00C4A7 60%, #0AB070 100%);
color: #ffffff;
border-radius: 999px;
padding: 14px 28px;
font-weight: 600;
font-weight: 700;
letter-spacing: -0.01em;
box-shadow: 0 10px 32px -8px rgba(168, 85, 247, 0.55),
0 0 0 1px rgba(255, 255, 255, 0.08) inset;
box-shadow: 0 12px 28px -10px rgba(3, 199, 90, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.14) inset;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.btn-grad:hover {
transform: translateY(-2px);
box-shadow: 0 16px 40px -8px rgba(168, 85, 247, 0.7),
0 0 0 1px rgba(255, 255, 255, 0.14) inset;
box-shadow: 0 18px 40px -10px rgba(3, 199, 90, 0.65),
0 0 0 1px rgba(255, 255, 255, 0.2) inset;
}
.btn-ghost {
background: rgba(255, 255, 255, 0.04);
color: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(255, 255, 255, 0.14);
background: #ffffff;
color: #0F1D17;
border: 1px solid rgba(15, 29, 23, 0.12);
border-radius: 999px;
padding: 14px 28px;
font-weight: 600;
backdrop-filter: blur(10px);
font-weight: 700;
transition: background 0.2s ease, border-color 0.2s ease, transform 0.25s ease;
}
.btn-ghost:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.28);
background: #F2FBF6;
border-color: #03C75A;
color: #02A149;
transform: translateY(-2px);
}
/* -----------------------------------------------------------------
* Card lift (legacy + dark variant)
* Card lift
* ----------------------------------------------------------------- */
.card-lift {
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.4s ease,
background 0.3s ease;
box-shadow: 0 2px 12px -6px rgba(15, 29, 23, 0.1);
}
.card-lift:hover {
transform: translateY(-6px);
transform: translateY(-4px);
box-shadow: 0 18px 40px -18px rgba(3, 199, 90, 0.25);
}
/* -----------------------------------------------------------------
* Link underline (legacy)
* Link underline
* ----------------------------------------------------------------- */
.link-underline {
position: relative;
@@ -349,14 +361,13 @@ body {
bottom: -2px;
width: 0;
height: 2px;
background: var(--color-warm-600);
background: #03C75A;
transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-underline:hover::after {
width: 100%;
}
/* Gradient underline for dark theme */
.link-grad {
position: relative;
display: inline-block;
@@ -368,7 +379,7 @@ body {
bottom: -3px;
width: 0;
height: 2px;
background: linear-gradient(90deg, #6366f1, #a855f7, #ec4899, #f97316);
background: linear-gradient(90deg, #03C75A, #00C4A7, #0AB070);
transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-grad:hover::after {
@@ -391,21 +402,19 @@ body {
}
/* -----------------------------------------------------------------
* Conic/orbit decorative ring
* Conic decorative ring
* ----------------------------------------------------------------- */
.conic-ring {
background: conic-gradient(
from 0deg,
#6366f1 0deg,
#a855f7 90deg,
#ec4899 180deg,
#f97316 270deg,
#6366f1 360deg
#03C75A 0deg,
#00C4A7 120deg,
#2BD673 240deg,
#03C75A 360deg
);
filter: blur(2px);
}
/* Reduce motion preference */
@media (prefers-reduced-motion: reduce) {
.reveal,
.animate-fade-up,
@@ -424,128 +433,138 @@ body {
}
/* =================================================================
* SITE-WIDE DARK THEME OVERRIDES
* Legacy pages (stores/blog/about/auth/faq/contact/admin/etc.)
* use warm-*/ink/white color classes. Remap them to dark equivalents
* here so every page inherits the new look without touching code.
* SITE-WIDE LIGHT THEME OVERRIDES
* Remap legacy color tokens (warm / ink / white / sage) to the
* new white + Naver green palette. All sub-pages inherit this
* without code changes.
* ================================================================= */
/* --- Page / section backgrounds (warm-50, warm-100) --- */
.bg-warm-50 { background-color: #05050f !important; }
.bg-warm-50\/50 { background-color: rgba(5, 5, 15, 0.6) !important; }
.bg-warm-50\/60 { background-color: rgba(5, 5, 15, 0.7) !important; }
.bg-warm-50\/70 { background-color: rgba(5, 5, 15, 0.75) !important; }
.bg-warm-50\/80 { background-color: rgba(5, 5, 15, 0.8) !important; }
/* --- Page / section backgrounds --- */
.bg-warm-50,
.bg-warm-50\/50,
.bg-warm-50\/60,
.bg-warm-50\/70,
.bg-warm-50\/80 { background-color: #ffffff !important; }
.bg-warm-100 { background-color: #0a0a18 !important; }
.bg-warm-100\/40 { background-color: rgba(10, 10, 24, 0.45) !important; }
.bg-warm-100\/50 { background-color: rgba(10, 10, 24, 0.55) !important; }
.bg-warm-100\/60 { background-color: rgba(10, 10, 24, 0.65) !important; }
.bg-warm-100\/70 { background-color: rgba(10, 10, 24, 0.75) !important; }
.bg-warm-100 { background-color: #F7FAF8 !important; }
.bg-warm-100\/40 { background-color: rgba(247, 250, 248, 0.6) !important; }
.bg-warm-100\/50 { background-color: rgba(247, 250, 248, 0.7) !important; }
.bg-warm-100\/60 { background-color: rgba(247, 250, 248, 0.82) !important; }
.bg-warm-100\/70 { background-color: rgba(247, 250, 248, 0.9) !important; }
.bg-warm-200 { background-color: #10101f !important; }
.bg-warm-200\/50 { background-color: rgba(16, 16, 31, 0.6) !important; }
.bg-warm-200 { background-color: #EFF2F0 !important; }
.bg-warm-200\/50 { background-color: rgba(239, 242, 240, 0.7) !important; }
/* --- Accent color tints (warm-400/500/600 → violet/iris/rose) --- */
.bg-warm-400\/15 { background-color: rgba(192, 132, 252, 0.14) !important; }
.bg-warm-400\/30 { background-color: rgba(192, 132, 252, 0.28) !important; }
.bg-warm-400\/40 { background-color: rgba(192, 132, 252, 0.38) !important; }
.bg-warm-500 { background-color: #a855f7 !important; color: white !important; }
.bg-warm-500\/10 { background-color: rgba(168, 85, 247, 0.12) !important; }
.bg-warm-500\/15 { background-color: rgba(168, 85, 247, 0.18) !important; }
.bg-warm-500\/20 { background-color: rgba(168, 85, 247, 0.24) !important; }
.bg-warm-600 { background-color: #ec4899 !important; color: white !important; }
.bg-warm-600\/90 { background-color: rgba(236, 72, 153, 0.92) !important; color: white !important; }
.bg-warm-700 { background-color: #6366f1 !important; color: white !important; }
.bg-warm-800 { background-color: #0f0f22 !important; }
.bg-warm-900 { background-color: #050510 !important; }
/* --- Accent tints (warm-400/500/600/700) → Naver green family --- */
.bg-warm-400\/15 { background-color: rgba(3, 199, 90, 0.12) !important; color: #02A149 !important; }
.bg-warm-400\/30 { background-color: rgba(3, 199, 90, 0.22) !important; color: #02A149 !important; }
.bg-warm-400\/40 { background-color: rgba(3, 199, 90, 0.32) !important; color: #02A149 !important; }
.bg-warm-500 { background-color: #03C75A !important; color: #ffffff !important; }
.bg-warm-500\/10 { background-color: rgba(3, 199, 90, 0.10) !important; }
.bg-warm-500\/15 { background-color: rgba(3, 199, 90, 0.16) !important; }
.bg-warm-500\/20 { background-color: rgba(3, 199, 90, 0.22) !important; }
.bg-warm-600 { background-color: #02A149 !important; color: #ffffff !important; }
.bg-warm-600\/90 { background-color: rgba(2, 161, 73, 0.92) !important; color: #ffffff !important; }
.bg-warm-700 { background-color: #018f40 !important; color: #ffffff !important; }
.bg-warm-800 { background-color: #0F1D17 !important; color: #ffffff !important; }
.bg-warm-900 { background-color: #0A1410 !important; color: #ffffff !important; }
/* --- Ink (dark) backgrounds now map to darkest navy --- */
.bg-ink { background-color: #05050f !important; }
.bg-ink\/5 { background-color: rgba(255, 255, 255, 0.04) !important; }
.bg-ink\/10 { background-color: rgba(255, 255, 255, 0.07) !important; }
.bg-ink\/20 { background-color: rgba(255, 255, 255, 0.12) !important; }
/* --- Ink (dark) backgrounds near-black text surfaces remain, tint utilities soft gray --- */
.bg-ink { background-color: #0F1D17 !important; color: #ffffff !important; }
.bg-ink\/5 { background-color: rgba(15, 29, 23, 0.04) !important; }
.bg-ink\/10 { background-color: rgba(15, 29, 23, 0.08) !important; }
.bg-ink\/20 { background-color: rgba(15, 29, 23, 0.14) !important; }
/* --- Sage (green) tints --- */
.bg-sage-500 { background-color: #34d399 !important; color: #05050f !important; }
.bg-sage-500\/5 { background-color: rgba(52, 211, 153, 0.08) !important; }
.bg-sage-500\/10 { background-color: rgba(52, 211, 153, 0.14) !important; }
.bg-sage-500\/15 { background-color: rgba(52, 211, 153, 0.2) !important; }
.bg-sage-500\/90 { background-color: rgba(52, 211, 153, 0.88) !important; color: #05050f !important; }
/* --- Sage → Naver green (preserve semantic meaning) --- */
.bg-sage-500 { background-color: #03C75A !important; color: #ffffff !important; }
.bg-sage-500\/5 { background-color: rgba(3, 199, 90, 0.06) !important; }
.bg-sage-500\/10 { background-color: rgba(3, 199, 90, 0.12) !important; }
.bg-sage-500\/15 { background-color: rgba(3, 199, 90, 0.18) !important; }
.bg-sage-500\/90 { background-color: rgba(3, 199, 90, 0.92) !important; color: #ffffff !important; }
/* --- White backgrounds → dark glass cards --- */
.bg-white {
background-color: rgba(16, 16, 31, 0.85) !important;
color: rgba(255, 255, 255, 0.92) !important;
}
.bg-white\/50 {
background-color: rgba(16, 16, 31, 0.55) !important;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.bg-white\/60 {
background-color: rgba(16, 16, 31, 0.6) !important;
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.bg-white\/70 {
background-color: rgba(16, 16, 31, 0.65) !important;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.bg-white\/80 {
background-color: rgba(16, 16, 31, 0.75) !important;
backdrop-filter: blur(22px);
-webkit-backdrop-filter: blur(22px);
}
.bg-white\/90 {
background-color: rgba(16, 16, 31, 0.85) !important;
}
/* --- White backgrounds — keep truly white --- */
.bg-white { background-color: #ffffff !important; color: #0F1D17 !important; }
.bg-white\/50 { background-color: rgba(255, 255, 255, 0.7) !important; backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
.bg-white\/60 { background-color: rgba(255, 255, 255, 0.78) !important; backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
.bg-white\/70 { background-color: rgba(255, 255, 255, 0.85) !important; backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); }
.bg-white\/80 { background-color: rgba(255, 255, 255, 0.9) !important; backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); }
.bg-white\/90 { background-color: rgba(255, 255, 255, 0.95) !important; }
/* --- Night backgrounds (dark legacy) → light --- */
.bg-night-0 { background-color: #ffffff !important; }
.bg-night-0\/70 { background-color: rgba(255, 255, 255, 0.82) !important; }
.bg-night-1 { background-color: #F7FAF8 !important; }
.bg-night-2 { background-color: #EFF2F0 !important; }
.bg-night-3 { background-color: #E4E8E6 !important; }
/* --- Text colors --- */
.text-ink { color: #ffffff !important; }
.text-ink-light { color: rgba(255, 255, 255, 0.68) !important; }
.text-ink-muted { color: rgba(255, 255, 255, 0.45) !important; }
.text-warm-50 { color: #ffffff !important; }
.text-warm-100 { color: rgba(255, 255, 255, 0.9) !important; }
.text-warm-200 { color: rgba(255, 255, 255, 0.72) !important; }
.text-warm-400 { color: #c084fc !important; }
.text-warm-500 { color: #a855f7 !important; }
.text-warm-600 { color: #ec4899 !important; }
.text-warm-700 { color: #818cf8 !important; }
.text-warm-800 { color: #c084fc !important; }
.text-warm-900 { color: #ffffff !important; }
.text-sage-500 { color: #34d399 !important; }
.text-sage-600 { color: #10b981 !important; }
.text-ink { color: #0F1D17 !important; }
.text-ink-light { color: #3E4A44 !important; }
.text-ink-muted { color: #7B8581 !important; }
.text-warm-50 { color: #ffffff !important; }
.text-warm-100 { color: #F7FAF8 !important; }
.text-warm-200 { color: #7B8581 !important; }
.text-warm-400 { color: #03C75A !important; }
.text-warm-500 { color: #03C75A !important; }
.text-warm-600 { color: #02A149 !important; }
.text-warm-700 { color: #018f40 !important; }
.text-warm-800 { color: #0F1D17 !important; }
.text-warm-900 { color: #0A1410 !important; }
.text-sage-500 { color: #03C75A !important; }
.text-sage-600 { color: #02A149 !important; }
.text-cloud-0 { color: #0F1D17 !important; }
.text-cloud-1 { color: rgba(15, 29, 23, 0.92) !important; }
.text-cloud-2 { color: rgba(15, 29, 23, 0.68) !important; }
.text-cloud-3 { color: rgba(15, 29, 23, 0.5) !important; }
.text-cloud-4 { color: rgba(15, 29, 23, 0.35) !important; }
/* --- Borders --- */
.border-ink { border-color: rgba(255, 255, 255, 0.92) !important; }
.border-ink\/5 { border-color: rgba(255, 255, 255, 0.05) !important; }
.border-ink\/10 { border-color: rgba(255, 255, 255, 0.08) !important; }
.border-ink\/15 { border-color: rgba(255, 255, 255, 0.12) !important; }
.border-ink\/20 { border-color: rgba(255, 255, 255, 0.16) !important; }
.border-ink\/40 { border-color: rgba(255, 255, 255, 0.3) !important; }
.border-warm-200 { border-color: rgba(255, 255, 255, 0.1) !important; }
.border-warm-200\/30{ border-color: rgba(255, 255, 255, 0.12) !important; }
.border-warm-200\/60{ border-color: rgba(255, 255, 255, 0.2) !important; }
.border-warm-500 { border-color: #a855f7 !important; }
.border-ink { border-color: rgba(15, 29, 23, 0.85) !important; }
.border-ink\/5 { border-color: rgba(15, 29, 23, 0.06) !important; }
.border-ink\/10 { border-color: rgba(15, 29, 23, 0.1) !important; }
.border-ink\/15 { border-color: rgba(15, 29, 23, 0.14) !important; }
.border-ink\/20 { border-color: rgba(15, 29, 23, 0.18) !important; }
.border-ink\/40 { border-color: rgba(15, 29, 23, 0.3) !important; }
.border-warm-200 { border-color: #EFF2F0 !important; }
.border-warm-200\/30 { border-color: rgba(239, 242, 240, 0.6) !important; }
.border-warm-200\/60 { border-color: rgba(228, 232, 230, 0.9) !important; }
.border-warm-300\/40 { border-color: rgba(3, 199, 90, 0.28) !important; }
.border-warm-500 { border-color: #03C75A !important; }
.border-white { border-color: #ffffff !important; }
.border-white\/5 { border-color: rgba(15, 29, 23, 0.05) !important; }
.border-white\/\[0\.06\] { border-color: rgba(15, 29, 23, 0.06) !important; }
.border-white\/\[0\.08\] { border-color: rgba(15, 29, 23, 0.08) !important; }
.border-white\/10 { border-color: rgba(15, 29, 23, 0.08) !important; }
.border-white\/15 { border-color: rgba(15, 29, 23, 0.1) !important; }
.border-white\/20 { border-color: rgba(15, 29, 23, 0.12) !important; }
.border-white\/30 { border-color: rgba(15, 29, 23, 0.18) !important; }
/* bg white/0.02, 0.03 etc (glass tints on dark theme) → very subtle gray */
.bg-white\/\[0\.02\] { background-color: #FAFCFB !important; }
.bg-white\/\[0\.03\] { background-color: #F7FAF8 !important; }
.bg-white\/\[0\.04\] { background-color: #F5F8F6 !important; }
.bg-white\/\[0\.05\] { background-color: #F2F6F4 !important; }
.bg-white\/\[0\.08\] { background-color: #EEF3F0 !important; }
/* --- Hover variants --- */
.hover\:bg-warm-50:hover { background-color: rgba(255, 255, 255, 0.03) !important; }
.hover\:bg-warm-100:hover { background-color: rgba(255, 255, 255, 0.05) !important; }
.hover\:bg-warm-200:hover { background-color: rgba(255, 255, 255, 0.08) !important; }
.hover\:bg-warm-400:hover { background-color: #c084fc !important; }
.hover\:bg-warm-500:hover { background-color: #a855f7 !important; }
.hover\:bg-warm-600:hover { background-color: #ec4899 !important; }
.hover\:bg-warm-700:hover { background-color: #6366f1 !important; }
.hover\:bg-warm-800:hover { background-color: #10101f !important; }
.hover\:text-warm-600:hover { color: #ec4899 !important; }
.hover\:text-warm-700:hover { color: #818cf8 !important; }
.hover\:text-warm-800:hover { color: #c084fc !important; }
.hover\:border-ink\/40:hover{ border-color: rgba(255, 255, 255, 0.3) !important; }
.hover\:border-warm-200:hover { border-color: rgba(255, 255, 255, 0.2) !important; }
.hover\:bg-warm-50:hover { background-color: #F7FAF8 !important; }
.hover\:bg-warm-100:hover { background-color: #EFF2F0 !important; }
.hover\:bg-warm-200:hover { background-color: #E4E8E6 !important; }
.hover\:bg-warm-400:hover { background-color: #2BD673 !important; color: #ffffff !important; }
.hover\:bg-warm-500:hover { background-color: #02A149 !important; color: #ffffff !important; }
.hover\:bg-warm-600:hover { background-color: #018f40 !important; color: #ffffff !important; }
.hover\:bg-warm-700:hover { background-color: #0F1D17 !important; color: #ffffff !important; }
.hover\:bg-warm-800:hover { background-color: #0A1410 !important; color: #ffffff !important; }
.hover\:text-warm-600:hover { color: #02A149 !important; }
.hover\:text-warm-700:hover { color: #018f40 !important; }
.hover\:text-warm-800:hover { color: #0F1D17 !important; }
.hover\:border-ink\/40:hover { border-color: rgba(15, 29, 23, 0.3) !important; }
.hover\:border-warm-200:hover { border-color: #03C75A !important; }
.hover\:text-cloud-0:hover { color: #03C75A !important; }
/* --- Form inputs: dark theme by default --- */
/* --- Form inputs: light theme --- */
input[type="text"],
input[type="email"],
input[type="password"],
@@ -559,64 +578,98 @@ input[type="datetime-local"],
input:not([type]),
textarea,
select {
background-color: rgba(16, 16, 31, 0.6) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: #ffffff !important;
background-color: #ffffff !important;
border: 1px solid #E4E8E6 !important;
color: #0F1D17 !important;
border-radius: 10px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input::placeholder,
textarea::placeholder {
color: rgba(255, 255, 255, 0.35) !important;
color: rgba(15, 29, 23, 0.42) !important;
}
input:focus,
textarea:focus,
select:focus {
border-color: #a855f7 !important;
box-shadow: 0 0 0 4px rgba(168, 85, 247, 0.18) !important;
border-color: #03C75A !important;
box-shadow: 0 0 0 4px rgba(3, 199, 90, 0.18) !important;
outline: none !important;
}
/* Checkbox / radio — keep accent purple */
input[type="checkbox"],
input[type="radio"] {
accent-color: #a855f7;
accent-color: #03C75A;
}
/* Option elements inside select */
select option {
background-color: #10101f;
color: #ffffff;
background-color: #ffffff;
color: #0F1D17;
}
/* --- Blog article prose (if used) --- */
/* --- Blog article prose --- */
.prose,
.prose-lg,
.prose-xl {
--tw-prose-body: rgba(255, 255, 255, 0.78);
--tw-prose-headings: #ffffff;
--tw-prose-lead: rgba(255, 255, 255, 0.72);
--tw-prose-links: #c084fc;
--tw-prose-bold: #ffffff;
--tw-prose-counters: rgba(255, 255, 255, 0.5);
--tw-prose-bullets: rgba(255, 255, 255, 0.35);
--tw-prose-hr: rgba(255, 255, 255, 0.1);
--tw-prose-quotes: rgba(255, 255, 255, 0.85);
--tw-prose-quote-borders: rgba(168, 85, 247, 0.5);
--tw-prose-captions: rgba(255, 255, 255, 0.5);
--tw-prose-code: #f472b6;
--tw-prose-pre-code: #e4e4e7;
--tw-prose-pre-bg: rgba(16, 16, 31, 0.75);
--tw-prose-th-borders: rgba(255, 255, 255, 0.15);
--tw-prose-td-borders: rgba(255, 255, 255, 0.08);
--tw-prose-body: #3E4A44;
--tw-prose-headings: #0F1D17;
--tw-prose-lead: #3E4A44;
--tw-prose-links: #02A149;
--tw-prose-bold: #0F1D17;
--tw-prose-counters: #7B8581;
--tw-prose-bullets: #B6BEB9;
--tw-prose-hr: #E4E8E6;
--tw-prose-quotes: #0F1D17;
--tw-prose-quote-borders: #03C75A;
--tw-prose-captions: #7B8581;
--tw-prose-code: #02A149;
--tw-prose-pre-code: #0F1D17;
--tw-prose-pre-bg: #F7FAF8;
--tw-prose-th-borders: #E4E8E6;
--tw-prose-td-borders: #EFF2F0;
}
/* --- Placeholder/disabled states --- */
*:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* --- Ensure html/body stay dark for non-home routes too --- */
html, body {
background-color: #05050f;
color-scheme: dark;
background-color: #ffffff;
color-scheme: light;
}
/* -----------------------------------------------------------------
* Mobile-first helpers for the app-style home layout
* ----------------------------------------------------------------- */
.category-tile {
position: relative;
overflow: hidden;
border-radius: 24px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.category-tile:active { transform: scale(0.98); }
.category-tile.primary {
background: linear-gradient(135deg, #03C75A 0%, #02A149 55%, #018f40 100%);
color: #ffffff;
box-shadow: 0 16px 40px -12px rgba(3, 199, 90, 0.45);
}
.category-tile.soft {
background: #F2FBF6;
color: #02A149;
border: 1px solid #D5EEE0;
}
.category-tile.neutral {
background: #ffffff;
color: #0F1D17;
border: 1px solid #E4E8E6;
}
/* Photo hero for property detail */
.photo-hero {
position: relative;
width: 100%;
aspect-ratio: 4 / 3;
overflow: hidden;
background: #EFF2F0;
}
@media (min-width: 768px) {
.photo-hero { aspect-ratio: 16 / 9; border-radius: 24px; }
}
+66 -26
View File
@@ -68,6 +68,8 @@ const OPERATOR_ROLES = [
const NAV_LINKS = [
{ href: '/stores', label: '매장 검색' },
{ href: '/stores/new', label: '매장 등록' },
{ href: '/demolition', label: '철거' },
{ href: '/closure', label: '폐업' },
{ href: '/matching', label: '매칭' },
{ href: '/subsidies', label: '지원금' },
{ href: '/vendors', label: '업체' },
@@ -75,22 +77,32 @@ const NAV_LINKS = [
{ href: '/faq', label: 'FAQ' },
];
const MOBILE_TABS = [
{ href: '/', label: '홈', icon: 'M3 12l9-9 9 9M5 10v10h14V10' },
{ href: '/stores', label: '매장', icon: 'M21 21l-4.35-4.35M16 10a6 6 0 10-12 0 6 6 0 0012 0z' },
{ href: '/matching', label: '매칭', icon: 'M7 8h10M7 12h6M7 16h10M3 4h18v16H3z' },
{ href: '/subsidies', label: '지원금', icon: 'M12 3v18M6 8h9a3 3 0 010 6H9a3 3 0 000 6h9' },
];
async function Navigation() {
const session = await auth();
const isOperator = session?.user && OPERATOR_ROLES.includes(session.user.primaryRole);
return (
<nav className="sticky top-0 z-50 border-b border-white/[0.06] bg-night-0/70 backdrop-blur-xl">
<nav
className="sticky top-0 z-50 border-b bg-white/85 backdrop-blur-xl"
style={{ borderColor: '#EFF2F0' }}
>
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-5">
<Link
href="/"
className="group flex items-center gap-2 font-display text-xl font-black tracking-tight text-cloud-0"
className="group flex items-center gap-2 font-display text-xl font-black tracking-tight text-ink"
>
<span
aria-hidden
className="relative inline-flex h-7 w-7 items-center justify-center rounded-md"
className="relative inline-flex h-8 w-8 items-center justify-center rounded-lg shadow-[0_6px_14px_-6px_rgba(3,199,90,0.55)]"
style={{
background: 'linear-gradient(135deg,#6366f1 0%,#a855f7 45%,#ec4899 80%,#f97316 100%)',
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
}}
>
<span className="text-sm font-black text-white">S</span>
@@ -104,7 +116,7 @@ async function Navigation() {
<Link
key={l.href}
href={l.href}
className="text-sm font-medium text-cloud-2 transition-colors hover:text-cloud-0"
className="text-sm font-medium text-ink-light transition-colors hover:text-ink"
>
{l.label}
</Link>
@@ -112,71 +124,97 @@ async function Navigation() {
{isOperator && (
<Link
href="/admin"
className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-sm text-cloud-2 transition-colors hover:border-white/20 hover:bg-white/[0.08] hover:text-cloud-0"
className="rounded-full border px-3 py-1.5 text-sm text-ink-light transition-colors hover:border-naver hover:text-naver"
style={{ borderColor: '#E4E8E6' }}
>
</Link>
)}
<AuthButtons session={session} />
</div>
{/* Mobile auth shortcut */}
<div className="flex items-center gap-2 md:hidden">
<AuthButtons session={session} />
</div>
</div>
</nav>
);
}
function MobileTabBar() {
return (
<nav
className="fixed bottom-0 left-0 right-0 z-40 grid grid-cols-4 border-t bg-white/95 backdrop-blur-xl md:hidden"
style={{ borderColor: '#EFF2F0' }}
>
{MOBILE_TABS.map((t) => (
<Link
key={t.href}
href={t.href}
className="flex flex-col items-center gap-1 py-2.5 text-[10px] font-semibold text-ink-light active:text-naver"
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.6} strokeLinecap="round" strokeLinejoin="round">
<path d={t.icon} />
</svg>
{t.label}
</Link>
))}
</nav>
);
}
function Footer() {
return (
<footer className="relative mt-24 border-t border-white/5 bg-night-0 font-body">
{/* decorative glow */}
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-60">
<footer className="relative mt-24 border-t bg-white font-body" style={{ borderColor: '#EFF2F0' }}>
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-40">
<div className="orb orb-iris absolute -left-32 top-10 h-72 w-72" />
<div className="orb orb-rose absolute right-0 bottom-0 h-72 w-72" />
</div>
<div className="relative mx-auto grid max-w-7xl grid-cols-2 gap-10 px-6 py-16 md:grid-cols-4">
<div>
<p className="font-display text-xl font-black tracking-tight text-cloud-0">
<p className="font-display text-xl font-black tracking-tight text-ink">
start<span className="text-grad-primary">over</span>
</p>
<p className="mt-3 text-xs leading-relaxed text-cloud-3">
<p className="mt-3 text-xs leading-relaxed text-ink-muted">
· ·
<br />
</p>
</div>
<div>
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-ink-muted">
Service
</p>
<ul className="mt-4 space-y-2.5 text-sm">
<li><Link href="/stores" className="text-cloud-2 hover:text-cloud-0 transition-colors"> </Link></li>
<li><Link href="/stores/new" className="text-cloud-2 hover:text-cloud-0 transition-colors"> </Link></li>
<li><Link href="/subsidies" className="text-cloud-2 hover:text-cloud-0 transition-colors"></Link></li>
<li><Link href="/vendors" className="text-cloud-2 hover:text-cloud-0 transition-colors"> </Link></li>
<li><Link href="/stores" className="text-ink-light hover:text-ink transition-colors"> </Link></li>
<li><Link href="/stores/new" className="text-ink-light hover:text-ink transition-colors"> </Link></li>
<li><Link href="/subsidies" className="text-ink-light hover:text-ink transition-colors"></Link></li>
<li><Link href="/vendors" className="text-ink-light hover:text-ink transition-colors"> </Link></li>
</ul>
</div>
<div>
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-ink-muted">
Learn
</p>
<ul className="mt-4 space-y-2.5 text-sm">
<li><Link href="/about" className="text-cloud-2 hover:text-cloud-0 transition-colors"> </Link></li>
<li><Link href="/blog" className="text-cloud-2 hover:text-cloud-0 transition-colors"></Link></li>
<li><Link href="/faq" className="text-cloud-2 hover:text-cloud-0 transition-colors"> </Link></li>
<li><Link href="/contact" className="text-cloud-2 hover:text-cloud-0 transition-colors"></Link></li>
<li><Link href="/about" className="text-ink-light hover:text-ink transition-colors"> </Link></li>
<li><Link href="/blog" className="text-ink-light hover:text-ink transition-colors"></Link></li>
<li><Link href="/faq" className="text-ink-light hover:text-ink transition-colors"> </Link></li>
<li><Link href="/contact" className="text-ink-light hover:text-ink transition-colors"></Link></li>
</ul>
</div>
<div>
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-ink-muted">
Legal
</p>
<ul className="mt-4 space-y-2.5 text-sm">
<li><Link href="/terms" className="text-cloud-2 hover:text-cloud-0 transition-colors"></Link></li>
<li><Link href="/privacy" className="text-cloud-2 hover:text-cloud-0 transition-colors"></Link></li>
<li><Link href="/terms" className="text-ink-light hover:text-ink transition-colors"></Link></li>
<li><Link href="/privacy" className="text-ink-light hover:text-ink transition-colors"></Link></li>
</ul>
</div>
</div>
<div className="relative border-t border-white/5 px-6 py-5 text-center font-mono text-[11px] tracking-[0.15em] text-cloud-4">
<div className="relative border-t px-6 py-5 text-center font-mono text-[11px] tracking-[0.15em] text-ink-muted" style={{ borderColor: '#EFF2F0' }}>
© 2026 STARTOVER · ALL RIGHTS RESERVED
</div>
</footer>
@@ -191,6 +229,7 @@ export default function RootLayout({
return (
<html lang="ko">
<head>
<meta name="theme-color" content="#03C75A" />
<Script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9505789508299290"
@@ -198,10 +237,11 @@ export default function RootLayout({
strategy="afterInteractive"
/>
</head>
<body className="min-h-screen bg-night-0 font-body text-cloud-1">
<body className="min-h-screen bg-white font-body text-ink pb-16 md:pb-0">
<Navigation />
{children}
<Footer />
<MobileTabBar />
</body>
</html>
);
+203 -293
View File
@@ -7,24 +7,21 @@ const PROCESS_STEPS = [
num: '01',
title: '매장 등록',
desc: '업종·지역·권리금·월매출 등 기본 정보를 입력하면 운영팀 검수를 거쳐 공개됩니다.',
accent: 'from-iris via-violet to-rose',
},
{
num: '02',
title: '자동 매칭',
desc: '등록된 매장은 인수 희망자와 인증된 철거·인테리어 업체에 동시에 노출됩니다.',
accent: 'from-violet via-rose to-tangerine',
},
{
num: '03',
title: '계약·정산',
desc: '표준 계약서, 에스크로 결제, 단계별 검수 승인으로 안전하게 거래가 마무리됩니다.',
accent: 'from-rose via-tangerine to-amber',
},
];
const STATS = [
{ value: '1', unit: '', label: '매장 등록', sub: '인수자·철거·인테리어 동시 매칭' },
{ value: '128', unit: '', label: '누적 매장 등록', sub: '인수자·철거·인테리어 동시 매칭' },
{ value: '7', unit: '개', label: '업종 대분류', sub: '49개 소분류까지 세분화' },
{ value: '24', unit: '주', label: '평균 매칭 기간', sub: '운영팀 검수 포함' },
{ value: '3', unit: '단계', label: '에스크로 정산', sub: '사전·중간·최종 검수' },
@@ -35,7 +32,6 @@ const BENEFITS = [
label: '매도인',
english: 'for sellers',
title: '깔끔한 마무리',
accent: 'iris',
points: [
'매장 한 번 등록으로 인수자·철거업체 동시 매칭',
'원상복구 비용을 인수자에게 이전하는 양도 우선 시도',
@@ -46,7 +42,6 @@ const BENEFITS = [
label: '매수인',
english: 'for founders',
title: '검증된 시작',
accent: 'violet',
points: [
'운영팀 검수를 통과한 매물만 공개',
'권리금·월매출·월수익·회수기간까지 한 페이지에 공개',
@@ -57,7 +52,6 @@ const BENEFITS = [
label: '업체',
english: 'for partners',
title: '안정적 수주',
accent: 'rose',
points: [
'인증 통과 후 지역·업종 맞춤 안건이 자동 전달',
'에스크로 선입금으로 대금 회수 리스크 최소화',
@@ -81,66 +75,6 @@ const FAQ_PREVIEW = [
},
];
const FEATURES = [
{
label: '폐업자',
english: 'Closing owners',
title: '깔끔한 마무리,\n새로운 시작',
desc: '매장 정보를 등록하면 철거비 절감, 시설 처분, 지원금 신청까지 한 번에 해결됩니다.',
href: '/stores/new',
cta: '매장 등록',
num: '01',
glow: 'iris',
},
{
label: '창업자',
english: 'Founders',
title: '검증된 매장,\n합리적 시작',
desc: '검증된 매장을 검색하고 매칭 요청을 보내 시설 인수와 인테리어 비용을 절감하세요.',
href: '/stores',
cta: '매장 검색',
num: '02',
glow: 'violet',
},
{
label: '철거·인테리어 업체',
english: 'Partners',
title: '안정적 수주,\n정확한 정산',
desc: '인증 업체로 등록하면 안정적인 수주와 정산을 보장받을 수 있습니다.',
href: '/vendors',
cta: '업체 인증',
num: '03',
glow: 'rose',
},
];
const SERVICES = [
{
title: '정부 지원금 가이드',
desc: '폐업 관련 정부 지원금 자격을 확인하고, 체크리스트와 서류 준비를 도와드립니다.',
href: '/subsidies',
cta: '지원금 확인',
tag: 'Guide',
icon: (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m-3-2.818.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
),
},
{
title: '안전한 거래',
desc: '표준 계약서, 에스크로 결제, 검수 승인 시스템으로 안전한 거래를 보장합니다.',
href: '/contracts',
cta: '계약 관리',
tag: 'Escrow',
icon: (
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 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>
),
},
];
const MARQUEE_ITEMS = [
'매장 한 번 등록',
'인수자 · 철거 · 인테리어 동시 매칭',
@@ -155,16 +89,93 @@ export default function HomePage() {
const latestPosts = getPostsSortedByDate().slice(0, 4);
return (
<main className="relative overflow-hidden bg-night-0 text-cloud-1 font-body">
<main className="relative overflow-hidden bg-white text-ink font-body">
{/* ==========================================================
* HERO
* MOBILE APP-STYLE QUICK ACCESS (visible only on small screens)
* - 매장 검색: 큰 박스 (primary)
* - 매장 등록: 작은 박스 (soft)
* - 매칭 / 지원금: 컴팩트 타일
* ========================================================== */}
<section className="relative min-h-[calc(100vh-4rem)] overflow-hidden">
{/* Animated grid */}
<section className="md:hidden px-5 pt-6 pb-2">
<div className="flex items-center gap-3">
<span className="inline-flex h-9 w-9 items-center justify-center rounded-xl text-white font-black" style={{ background: 'linear-gradient(135deg,#03C75A,#02A149)' }}>S</span>
<div>
<p className="text-[11px] font-semibold tracking-[0.18em] text-naver-dark" style={{ color: '#02A149' }}>START OVER</p>
<p className="text-[13px] text-ink-muted"> · · </p>
</div>
</div>
<div className="mt-5 grid grid-cols-3 grid-rows-2 gap-3">
{/* 매장 검색 — 큰 박스 (2×2) */}
<Link
href="/stores"
className="category-tile primary col-span-2 row-span-2 flex flex-col justify-between p-6"
>
<div>
<p className="text-[11px] font-semibold tracking-[0.2em] opacity-90">SEARCH</p>
<h2 className="mt-2 font-display text-2xl font-extrabold leading-tight">
</h2>
<p className="mt-2 text-[13px] leading-relaxed opacity-95">
··
</p>
</div>
<div className="mt-4 flex items-center justify-between">
<span className="text-[11px] font-semibold opacity-90">128 </span>
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-white/20 text-xl"></span>
</div>
</Link>
{/* 매장 등록 — 작은 박스 */}
<Link
href="/stores/new"
className="category-tile soft flex flex-col justify-between p-4"
>
<div>
<p className="text-[10px] font-semibold tracking-[0.2em] opacity-80">REGISTER</p>
<h3 className="mt-1 font-display text-base font-extrabold"> </h3>
</div>
<span className="text-[11px] font-semibold">3 </span>
</Link>
{/* 매칭 */}
<Link
href="/matching"
className="category-tile neutral flex flex-col justify-between p-4"
>
<div>
<p className="text-[10px] font-semibold tracking-[0.2em] text-ink-muted">MATCH</p>
<h3 className="mt-1 font-display text-base font-extrabold"> </h3>
</div>
<span className="text-[11px] font-semibold" style={{ color: '#02A149' }}></span>
</Link>
</div>
<div className="mt-3 grid grid-cols-4 gap-2">
{[
{ href: '/subsidies', label: '지원금', emoji: '💰' },
{ href: '/vendors', label: '업체', emoji: '🛠' },
{ href: '/blog', label: '가이드', emoji: '📖' },
{ href: '/faq', label: 'FAQ', emoji: '💬' },
].map((q) => (
<Link
key={q.href}
href={q.href}
className="flex flex-col items-center gap-1 rounded-2xl border border-line bg-white px-2 py-3 text-center"
>
<span className="text-lg leading-none">{q.emoji}</span>
<span className="text-[11px] font-semibold text-ink">{q.label}</span>
</Link>
))}
</div>
</section>
{/* ==========================================================
* HERO (desktop focus, mobile still shows it below quick-access)
* ========================================================== */}
<section className="relative overflow-hidden md:min-h-[calc(100vh-4rem)]">
<div className="pointer-events-none absolute inset-0 bg-grid-animated opacity-80" />
{/* Radial fade to edges */}
<div className="pointer-events-none absolute inset-0 bg-radial-fade" />
{/* Glow orbs */}
<div className="pointer-events-none absolute inset-0">
<div className="orb orb-iris absolute -top-20 -left-20 h-[520px] w-[520px]" style={{ animationDelay: '0s' }} />
<div className="orb orb-violet absolute top-1/4 right-0 h-[440px] w-[440px]" style={{ animationDelay: '-4s' }} />
@@ -172,30 +183,25 @@ export default function HomePage() {
<div className="orb orb-tangerine absolute -bottom-24 right-1/3 h-[320px] w-[320px] opacity-40" style={{ animationDelay: '-12s' }} />
</div>
{/* Hero content */}
<div className="relative mx-auto flex min-h-[calc(100vh-4rem)] max-w-7xl flex-col justify-center px-6 py-24">
<p className="reveal d1 font-mono text-xs tracking-[0.3em] uppercase text-cloud-3">
<span className="inline-block h-1.5 w-1.5 translate-y-[-2px] rounded-full bg-lime-light align-middle mr-2 animate-pulse" />
<div className="relative mx-auto flex md:min-h-[calc(100vh-4rem)] max-w-7xl flex-col justify-center px-6 py-16 md:py-24">
<p className="reveal d1 font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="inline-block h-1.5 w-1.5 translate-y-[-2px] rounded-full align-middle mr-2 animate-pulse" style={{ background: '#03C75A' }} />
· ·
</p>
<h1 className="reveal d2 mt-6 font-display text-[clamp(3.5rem,11vw,10rem)] font-black leading-[0.95] tracking-tight text-cloud-0">
<h1 className="reveal d2 mt-6 font-display text-[clamp(3rem,10vw,9rem)] font-black leading-[0.95] tracking-tight text-ink">
Start<br />
<span className="text-grad-rainbow">
over
</span>
<span className="font-serif italic text-cloud-1" style={{ fontWeight: 400 }}>
.
</span>
<span className="text-grad-rainbow">over</span>
<span className="font-serif italic text-ink" style={{ fontWeight: 400 }}>.</span>
</h1>
<p className="reveal d3 mt-10 max-w-2xl text-lg leading-relaxed text-cloud-2 sm:text-xl">
<span className="font-serif italic text-cloud-0"> </span>
<p className="reveal d3 mt-8 max-w-2xl text-lg leading-relaxed text-ink-light sm:text-xl">
<span className="font-serif italic text-ink"> </span>
· · ,
.
</p>
<div className="reveal d4 mt-12 flex flex-wrap items-center gap-4">
<div className="reveal d4 mt-10 flex flex-wrap items-center gap-4">
<Link href="/stores" className="btn-grad group inline-flex items-center gap-2 text-base">
<span className="inline-block transition-transform group-hover:translate-x-1"></span>
@@ -206,8 +212,7 @@ export default function HomePage() {
</Link>
</div>
{/* Floating stats pills */}
<div className="reveal d5 mt-20 flex flex-wrap items-center gap-3">
<div className="reveal d5 mt-16 flex flex-wrap items-center gap-3">
{[
{ k: '업종', v: '7개 대분류' },
{ k: '세분류', v: '49개' },
@@ -216,32 +221,26 @@ export default function HomePage() {
].map((p) => (
<span
key={p.k}
className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-xs text-cloud-2 backdrop-blur-md"
className="inline-flex items-center gap-2 rounded-full border border-line bg-white px-4 py-2 text-xs text-ink-light"
>
<span className="font-mono text-[10px] uppercase tracking-widest text-cloud-4">{p.k}</span>
<span className="text-cloud-1">{p.v}</span>
<span className="font-mono text-[10px] uppercase tracking-widest text-ink-muted">{p.k}</span>
<span className="text-ink">{p.v}</span>
</span>
))}
</div>
{/* Scroll indicator */}
<div className="reveal d6 absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2">
<span className="font-mono text-[10px] uppercase tracking-[0.3em] text-cloud-4">scroll</span>
<span className="h-10 w-px bg-gradient-to-b from-cloud-3 to-transparent" />
</div>
</div>
</section>
{/* ==========================================================
* MARQUEE
* ========================================================== */}
<section className="relative border-y border-white/5 bg-night-1 py-8">
<section className="relative border-y border-line bg-surface-soft py-8" style={{ background: '#F7FAF8' }}>
<div className="marquee-mask overflow-hidden">
<div className="marquee-track">
{[...MARQUEE_ITEMS, ...MARQUEE_ITEMS].map((item, i) => (
<span key={i} className="flex items-center gap-12 font-serif text-2xl italic text-cloud-2 md:text-3xl">
<span key={i} className="flex items-center gap-12 font-serif text-2xl italic text-ink-light md:text-3xl">
{item}
<span className="inline-block h-1.5 w-1.5 rounded-full bg-gradient-to-r from-iris to-rose" />
<span className="inline-block h-1.5 w-1.5 rounded-full" style={{ background: 'linear-gradient(90deg,#03C75A,#00C4A7)' }} />
</span>
))}
</div>
@@ -249,62 +248,48 @@ export default function HomePage() {
</section>
{/* ==========================================================
* FOR WHO — 3 big role cards
* FOR WHO — 3 role cards (desktop primary)
* ========================================================== */}
<section className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-16 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<section className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<div>
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 01 · For Who
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl md:text-6xl">
<span className="font-serif italic font-normal text-cloud-2"></span>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl md:text-6xl">
<span className="font-serif italic font-normal text-ink-light"></span>
<br />
</h2>
</div>
<p className="max-w-md text-sm leading-relaxed text-cloud-2">
<p className="max-w-md text-sm leading-relaxed text-ink-light">
· ·
.
</p>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{FEATURES.map((f, i) => (
{[
{ label: '폐업자', english: 'Closing owners', title: '깔끔한 마무리,\n새로운 시작', desc: '매장 정보를 등록하면 철거비 절감, 시설 처분, 지원금 신청까지 한 번에 해결됩니다.', href: '/stores/new', cta: '매장 등록', num: '01' },
{ label: '창업자', english: 'Founders', title: '검증된 매장,\n합리적 시작', desc: '검증된 매장을 검색하고 매칭 요청을 보내 시설 인수와 인테리어 비용을 절감하세요.', href: '/stores', cta: '매장 검색', num: '02' },
{ label: '철거·인테리어 업체', english: 'Partners', title: '안정적 수주,\n정확한 정산', desc: '인증 업체로 등록하면 안정적인 수주와 정산을 보장받을 수 있습니다.', href: '/vendors', cta: '업체 인증', num: '03' },
].map((f) => (
<Link
key={f.num}
href={f.href}
className="grad-border group relative overflow-hidden p-8"
style={{ minHeight: 380 }}
style={{ minHeight: 360 }}
>
{/* Glow accent */}
<div
className={`pointer-events-none absolute -top-20 -right-20 h-48 w-48 rounded-full opacity-30 blur-3xl transition-opacity group-hover:opacity-70 ${
f.glow === 'iris' ? 'bg-iris' : f.glow === 'violet' ? 'bg-violet' : 'bg-rose'
}`}
/>
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
{f.english}
</span>
<p className="mt-2 inline-flex items-center gap-2 text-xs font-medium tracking-widest uppercase text-cloud-2">
<span className="font-serif text-5xl font-normal not-italic leading-none text-grad-primary">
{f.num}
</span>
<div className="pointer-events-none absolute -top-20 -right-20 h-48 w-48 rounded-full opacity-50 blur-3xl transition-opacity group-hover:opacity-80" style={{ background: 'radial-gradient(circle,#03C75A,transparent 70%)' }} />
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-ink-muted">{f.english}</span>
<p className="mt-2 inline-flex items-center gap-2 text-xs font-medium tracking-widest uppercase text-ink-light">
<span className="font-serif text-5xl font-normal not-italic leading-none text-grad-primary">{f.num}</span>
<span className="ml-2">{f.label}</span>
</p>
<h3 className="mt-8 font-display text-2xl font-bold leading-[1.2] whitespace-pre-line text-cloud-0">
{f.title}
</h3>
<p className="mt-4 text-sm leading-relaxed text-cloud-2">
{f.desc}
</p>
<span className="absolute bottom-8 left-8 inline-flex items-center gap-2 text-sm font-semibold text-cloud-1 transition-transform group-hover:translate-x-1">
{f.cta}
<span className="text-grad-primary text-base"></span>
<h3 className="mt-8 font-display text-2xl font-bold leading-[1.2] whitespace-pre-line text-ink">{f.title}</h3>
<p className="mt-4 text-sm leading-relaxed text-ink-light">{f.desc}</p>
<span className="absolute bottom-8 left-8 inline-flex items-center gap-2 text-sm font-semibold text-ink transition-transform group-hover:translate-x-1">
{f.cta}<span className="text-grad-primary text-base"></span>
</span>
</Link>
))}
@@ -314,24 +299,23 @@ export default function HomePage() {
{/* ==========================================================
* PROCESS
* ========================================================== */}
<section className="relative border-y border-white/5 bg-night-1">
{/* subtle decoration */}
<section className="relative border-y border-line" style={{ background: '#F7FAF8' }}>
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-50">
<div className="orb orb-iris absolute -top-20 left-0 h-80 w-80" />
<div className="orb orb-tangerine absolute -bottom-20 right-0 h-80 w-80" />
</div>
<div className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-16 max-w-3xl">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<div className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12 max-w-3xl">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 02 · Process
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl md:text-6xl">
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl md:text-6xl">
,
<br />
<span className="text-grad-rainbow"> </span>
</h2>
<p className="mt-6 text-cloud-2">
<p className="mt-6 text-ink-light">
· · · ·
.
</p>
@@ -340,24 +324,20 @@ export default function HomePage() {
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{PROCESS_STEPS.map((step, i) => (
<div key={step.num} className="relative">
{/* Connector line (only between cards on desktop) */}
{i < PROCESS_STEPS.length - 1 && (
<div className="hidden md:block absolute top-14 left-full z-0 h-px w-6 bg-gradient-to-r from-white/10 to-transparent" />
<div className="hidden md:block absolute top-14 left-full z-0 h-px w-6 bg-gradient-to-r from-naver to-transparent" style={{ background: 'linear-gradient(90deg,rgba(3,199,90,0.35),transparent)' }} />
)}
<div className="glass-darker relative z-10 rounded-2xl p-8">
<div className="relative z-10 rounded-2xl border border-line bg-white p-8 shadow-[0_2px_12px_-8px_rgba(15,29,23,0.1)]">
<div className="flex items-center gap-4">
<span
className={`inline-flex h-14 w-14 items-center justify-center rounded-xl bg-gradient-to-br ${step.accent} font-mono text-lg font-bold text-white shadow-[0_8px_32px_-8px_rgba(168,85,247,0.45)]`}
className="inline-flex h-14 w-14 items-center justify-center rounded-xl font-mono text-lg font-bold text-white shadow-[0_10px_24px_-8px_rgba(3,199,90,0.5)]"
style={{ background: 'linear-gradient(135deg,#03C75A 0%,#02A149 60%,#018f40 100%)' }}
>
{step.num}
</span>
<h3 className="font-display text-2xl font-bold text-cloud-0">
{step.title}
</h3>
<h3 className="font-display text-2xl font-bold text-ink">{step.title}</h3>
</div>
<p className="mt-5 text-sm leading-relaxed text-cloud-2">
{step.desc}
</p>
<p className="mt-5 text-sm leading-relaxed text-ink-light">{step.desc}</p>
</div>
</div>
))}
@@ -368,24 +348,24 @@ export default function HomePage() {
{/* ==========================================================
* STATS
* ========================================================== */}
<section className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-14">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<section className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 03 · Numbers
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl">
<span className="font-serif italic font-normal text-cloud-2">startover</span>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl">
<span className="font-serif italic font-normal text-ink-light">startover</span>
</h2>
</div>
<div className="grid grid-cols-2 gap-6 md:grid-cols-4">
{STATS.map((s, i) => (
<div key={s.label} className="glass-darker rounded-2xl p-7">
{STATS.map((s) => (
<div key={s.label} className="rounded-2xl border border-line bg-white p-7 shadow-[0_2px_12px_-8px_rgba(15,29,23,0.1)]">
<p className="flex items-baseline gap-1 font-display">
<span className="text-6xl font-black tracking-tight text-grad-primary">{s.value}</span>
<span className="text-xl font-bold text-cloud-2">{s.unit}</span>
<span className="text-xl font-bold text-ink-light">{s.unit}</span>
</p>
<p className="mt-4 text-sm font-semibold text-cloud-1">{s.label}</p>
<p className="mt-1 text-xs text-cloud-3">{s.sub}</p>
<p className="mt-4 text-sm font-semibold text-ink">{s.label}</p>
<p className="mt-1 text-xs text-ink-muted">{s.sub}</p>
</div>
))}
</div>
@@ -394,40 +374,41 @@ export default function HomePage() {
{/* ==========================================================
* CATEGORIES
* ========================================================== */}
<section className="relative border-y border-white/5 bg-night-1">
<section className="relative border-y border-line" style={{ background: '#F7FAF8' }}>
<div className="pointer-events-none absolute inset-0 bg-dot opacity-40" />
<div className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-14 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<div className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<div>
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 04 · Categories
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl">
<span className="text-grad-rose"></span>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl">
<span className="text-grad-primary"></span>
</h2>
<p className="mt-5 max-w-xl text-cloud-2">
<p className="mt-5 max-w-xl text-ink-light">
7 ,
49 .
</p>
</div>
<Link
href="/stores"
className="link-grad hidden text-sm font-semibold text-cloud-1 md:inline-flex md:items-center md:gap-2"
className="link-grad hidden text-sm font-semibold text-ink md:inline-flex md:items-center md:gap-2"
>
<span className="text-grad-primary"></span>
</Link>
</div>
<div className="grid grid-cols-2 gap-3 md:grid-cols-4 lg:grid-cols-7">
{INDUSTRY_MAJORS.map((m, i) => (
{INDUSTRY_MAJORS.map((m) => (
<Link
key={m.code}
href={`/stores?industryMajor=${m.code}`}
className="group relative overflow-hidden rounded-2xl border border-white/[0.08] bg-white/[0.02] p-5 text-center transition-all hover:-translate-y-1 hover:border-white/20 hover:bg-white/[0.05]"
className="group relative overflow-hidden rounded-2xl border border-line bg-white p-5 text-center transition-all hover:-translate-y-1 hover:border-naver hover:shadow-[0_12px_24px_-12px_rgba(3,199,90,0.3)]"
style={{ borderColor: '#E4E8E6' }}
>
<p className="font-display text-base font-bold text-cloud-0 transition-colors group-hover:text-grad-primary">
<p className="font-display text-base font-bold text-ink transition-colors group-hover:text-grad-primary">
{m.label}
</p>
<p className="mt-1 font-mono text-[10px] text-cloud-4">
<p className="mt-1 font-mono text-[10px] text-ink-muted">
{m.children.length} sub
</p>
</Link>
@@ -439,12 +420,12 @@ export default function HomePage() {
{/* ==========================================================
* BENEFITS
* ========================================================== */}
<section className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-16 max-w-3xl">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<section className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12 max-w-3xl">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 05 · Benefits
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl md:text-6xl">
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl md:text-6xl">
<br />
<span className="text-grad-primary"></span>
@@ -453,35 +434,18 @@ export default function HomePage() {
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{BENEFITS.map((b) => (
<div key={b.label} className="grad-border group relative overflow-hidden p-8">
<div
className={`pointer-events-none absolute -top-24 -right-16 h-48 w-48 rounded-full opacity-25 blur-3xl transition-opacity group-hover:opacity-60 ${
b.accent === 'iris' ? 'bg-iris' : b.accent === 'violet' ? 'bg-violet' : 'bg-rose'
}`}
/>
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
{b.english}
</p>
<p className="mt-1 text-xs font-semibold tracking-widest uppercase text-cloud-2">
{b.label}
</p>
<h3 className="mt-4 font-display text-3xl font-bold text-cloud-0">
{b.title}
</h3>
<div className="pointer-events-none absolute -top-24 -right-16 h-48 w-48 rounded-full opacity-40 blur-3xl transition-opacity group-hover:opacity-70" style={{ background: 'radial-gradient(circle,#03C75A,transparent 70%)' }} />
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-ink-muted">{b.english}</p>
<p className="mt-1 text-xs font-semibold tracking-widest uppercase text-ink-light">{b.label}</p>
<h3 className="mt-4 font-display text-3xl font-bold text-ink">{b.title}</h3>
<ul className="mt-7 space-y-4">
{b.points.map((p, idx) => (
<li key={p} className="flex gap-3 text-sm leading-relaxed text-cloud-2">
{b.points.map((p) => (
<li key={p} className="flex gap-3 text-sm leading-relaxed text-ink-light">
<span
className="mt-1.5 h-2 w-2 shrink-0 rounded-full"
style={{
background:
b.accent === 'iris'
? 'linear-gradient(135deg,#818cf8,#6366f1)'
: b.accent === 'violet'
? 'linear-gradient(135deg,#c084fc,#a855f7)'
: 'linear-gradient(135deg,#f472b6,#ec4899)',
boxShadow: `0 0 8px ${
b.accent === 'iris' ? '#6366f1' : b.accent === 'violet' ? '#a855f7' : '#ec4899'
}`,
background: 'linear-gradient(135deg,#03C75A,#02A149)',
boxShadow: '0 0 8px rgba(3,199,90,0.6)',
}}
/>
{p}
@@ -493,76 +457,26 @@ export default function HomePage() {
</div>
</section>
{/* ==========================================================
* SERVICES
* ========================================================== */}
<section className="relative border-y border-white/5 bg-night-1">
<div className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-16 max-w-3xl">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<span className="text-grad-primary"></span> 06 · Services
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl">
</h2>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{SERVICES.map((s) => (
<Link
key={s.title}
href={s.href}
className="grad-border group relative flex gap-6 p-8"
>
<div
className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl text-white shadow-[0_10px_30px_-8px_rgba(168,85,247,0.5)]"
style={{
background:
'linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%)',
}}
>
{s.icon}
</div>
<div className="flex-1">
<p className="font-mono text-[10px] tracking-[0.25em] uppercase text-cloud-4">
{s.tag}
</p>
<h3 className="mt-1 font-display text-2xl font-bold text-cloud-0">
{s.title}
</h3>
<p className="mt-3 text-sm leading-relaxed text-cloud-2">
{s.desc}
</p>
<span className="link-grad mt-5 inline-flex items-center gap-2 text-sm font-semibold text-cloud-1">
{s.cta}
<span className="text-grad-primary"></span>
</span>
</div>
</Link>
))}
</div>
</div>
</section>
{/* ==========================================================
* BLOG
* ========================================================== */}
<section className="relative mx-auto max-w-7xl px-6 py-28">
<div className="mb-14 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<section className="relative mx-auto max-w-7xl px-6 py-20 md:py-28">
<div className="mb-12 flex flex-col items-start justify-between gap-6 md:flex-row md:items-end">
<div>
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<span className="text-grad-primary"></span> 07 · Journal
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 06 · Journal
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl">
<span className="font-serif italic font-normal text-cloud-2"></span>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl">
<span className="font-serif italic font-normal text-ink-light"></span>
</h2>
<p className="mt-5 max-w-xl text-cloud-2">
<p className="mt-5 max-w-xl text-ink-light">
, ,
.
</p>
</div>
<Link
href="/blog"
className="link-grad hidden text-sm font-semibold text-cloud-1 md:inline-flex md:items-center md:gap-2"
className="link-grad hidden text-sm font-semibold text-ink md:inline-flex md:items-center md:gap-2"
>
<span className="text-grad-primary"></span>
</Link>
@@ -572,18 +486,19 @@ export default function HomePage() {
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className="group flex flex-col rounded-2xl border border-white/[0.08] bg-white/[0.02] p-6 transition-all hover:-translate-y-1 hover:border-white/20 hover:bg-white/[0.05]"
className="group flex flex-col rounded-2xl border border-line bg-white p-6 transition-all hover:-translate-y-1 hover:border-naver hover:shadow-[0_18px_30px_-18px_rgba(3,199,90,0.35)]"
style={{ borderColor: '#E4E8E6' }}
>
<span className="inline-flex w-fit rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 font-mono text-[10px] tracking-widest uppercase text-cloud-2">
<span className="inline-flex w-fit rounded-full border border-line bg-surface-soft px-2.5 py-1 font-mono text-[10px] tracking-widest uppercase text-ink-light" style={{ background: '#F2FBF6', borderColor: '#D5EEE0' }}>
{post.category}
</span>
<h3 className="mt-4 flex-1 font-display font-bold leading-snug text-cloud-0 transition-colors group-hover:text-grad-primary line-clamp-2">
<h3 className="mt-4 flex-1 font-display font-bold leading-snug text-ink transition-colors group-hover:text-grad-primary line-clamp-2">
{post.title}
</h3>
<p className="mt-3 text-xs leading-relaxed text-cloud-3 line-clamp-3">
<p className="mt-3 text-xs leading-relaxed text-ink-muted line-clamp-3">
{post.description}
</p>
<p className="mt-5 font-mono text-[10px] tracking-wider text-cloud-4">
<p className="mt-5 font-mono text-[10px] tracking-wider text-ink-muted">
{post.publishedAt} · {post.readMinutes}min
</p>
</Link>
@@ -594,17 +509,17 @@ export default function HomePage() {
{/* ==========================================================
* FAQ
* ========================================================== */}
<section className="relative border-y border-white/5 bg-night-1">
<section className="relative border-y border-line" style={{ background: '#F7FAF8' }}>
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-60">
<div className="orb orb-violet absolute top-0 right-0 h-96 w-96" />
<div className="orb orb-iris absolute bottom-0 left-0 h-96 w-96" />
</div>
<div className="relative mx-auto max-w-4xl px-6 py-28">
<div className="mb-14 text-center">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-4">
<span className="text-grad-primary"></span> 08 · FAQ
<div className="relative mx-auto max-w-4xl px-6 py-20 md:py-28">
<div className="mb-12 text-center">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> 07 · FAQ
</p>
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-cloud-0 sm:text-5xl">
<h2 className="mt-4 font-display text-4xl font-black tracking-tight text-ink sm:text-5xl">
<span className="text-grad-primary"></span>
</h2>
</div>
@@ -612,22 +527,21 @@ export default function HomePage() {
{FAQ_PREVIEW.map((f, i) => (
<details
key={i}
className="group glass-darker rounded-2xl p-6 open:border-white/20 transition-colors"
className="group rounded-2xl border border-line bg-white p-6 open:border-naver transition-colors shadow-[0_2px_12px_-8px_rgba(15,29,23,0.08)]"
style={{ borderColor: '#E4E8E6' }}
>
<summary className="flex cursor-pointer items-start justify-between gap-4 list-none">
<span className="font-display text-base font-semibold text-cloud-0">{f.q}</span>
<span className="mt-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-white/15 text-sm text-cloud-2 transition-transform group-open:rotate-45 group-open:border-white/30 group-open:text-cloud-0">
+
</span>
<span className="font-display text-base font-semibold text-ink">{f.q}</span>
<span className="mt-1 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-line text-sm text-ink-light transition-transform group-open:rotate-45" style={{ borderColor: '#E4E8E6' }}>+</span>
</summary>
<p className="mt-4 text-sm leading-relaxed text-cloud-2">{f.a}</p>
<p className="mt-4 text-sm leading-relaxed text-ink-light">{f.a}</p>
</details>
))}
</div>
<div className="mt-12 text-center">
<div className="mt-10 text-center">
<Link
href="/faq"
className="inline-flex items-center gap-2 font-mono text-xs tracking-widest uppercase text-cloud-2 hover:text-cloud-0 transition-colors"
className="inline-flex items-center gap-2 font-mono text-xs tracking-widest uppercase text-ink-light hover:text-naver transition-colors"
>
FAQ <span className="text-grad-primary"></span>
</Link>
@@ -639,26 +553,22 @@ export default function HomePage() {
* FINAL CTA
* ========================================================== */}
<section className="relative overflow-hidden">
{/* Massive gradient background */}
<div
className="pointer-events-none absolute inset-0 opacity-90"
className="pointer-events-none absolute inset-0 opacity-95"
style={{
background:
'radial-gradient(ellipse at 30% 40%, #6366f1 0%, transparent 55%), radial-gradient(ellipse at 70% 60%, #ec4899 0%, transparent 55%), radial-gradient(ellipse at 50% 100%, #f97316 0%, transparent 65%), #05050f',
'radial-gradient(ellipse at 30% 40%, rgba(3,199,90,0.2) 0%, transparent 55%), radial-gradient(ellipse at 70% 60%, rgba(0,196,167,0.18) 0%, transparent 55%), radial-gradient(ellipse at 50% 100%, rgba(43,214,115,0.14) 0%, transparent 65%), #ffffff',
}}
/>
<div className="pointer-events-none absolute inset-0 bg-grid opacity-30" />
<div className="pointer-events-none absolute inset-0 bg-radial-fade opacity-50" />
<div className="pointer-events-none absolute inset-0 bg-grid opacity-40" />
<div className="relative mx-auto max-w-5xl px-6 py-32 text-center">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-cloud-3">
Get started
</p>
<h2 className="mt-5 font-display text-5xl font-black tracking-tight text-cloud-0 sm:text-6xl md:text-7xl">
<span className="font-serif italic font-normal text-cloud-0"></span>,<br />
<div className="relative mx-auto max-w-5xl px-6 py-24 md:py-32 text-center">
<p className="font-mono text-xs tracking-[0.3em] uppercase text-ink-muted">Get started</p>
<h2 className="mt-5 font-display text-5xl font-black tracking-tight text-ink sm:text-6xl md:text-7xl">
<span className="font-serif italic font-normal text-ink"></span>,<br />
<span className="text-grad-rainbow"> </span>
</h2>
<p className="mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-cloud-2">
<p className="mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-ink-light">
· · · .
<br className="hidden md:block" />
.
@@ -0,0 +1,216 @@
'use server';
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';
import { createPrismaClient } from '@startover/database';
import { auth } from '@/lib/auth';
export type StoreEditFormState = {
error?: string;
fieldValues?: Record<string, string | undefined>;
};
const MAN = 10000;
const toWonOrZero = (v: string | null) => (v ? Number(v) * MAN : 0);
const toWon = (v: string | null) => (v ? Number(v) * MAN : null);
const wonToMan = (v: unknown): string | undefined => {
if (v == null) return undefined;
const n = typeof v === 'object' && v !== null && 'toString' in v
? Number((v as { toString(): string }).toString())
: Number(v);
if (!Number.isFinite(n)) return undefined;
return String(Math.round(n / MAN));
};
/**
* 기존 store 데이터를 만원 단위로 변환해서 폼 초기값으로 반환.
*/
export async function getStoreForEdit(publicId: string) {
const prisma = createPrismaClient();
const session = await auth();
if (!session?.user?.dbId) return null;
const store = await prisma.store.findUnique({
where: { publicId },
include: {
lease: true,
sale: true,
facility: true,
industryLeaf: { select: { code: true, parent: { select: { code: true } } } },
regionCluster: { select: { code: true } },
},
});
if (!store) return null;
if (store.ownerUserId !== BigInt(session.user.dbId)) return null;
if (store.reviewStatus !== 'DRAFT' && store.reviewStatus !== 'REJECTED') return null;
return {
publicId: store.publicId,
listingTitle: store.listingTitle,
regionClusterCode: store.regionCluster?.code ?? '',
industryMajorCode: store.industryLeaf?.parent?.code ?? '',
industryLeafCode: store.industryLeaf?.code ?? '',
roadAddress: store.roadAddress,
depositAmount: wonToMan(store.lease?.depositAmount),
monthlyRentAmount: wonToMan(store.lease?.monthlyRentAmount),
premiumAmount: wonToMan(store.lease?.premiumAmount ?? store.sale?.premiumAmount),
remainingLeaseMonths:
store.lease?.remainingLeaseMonths != null
? String(store.lease.remainingLeaseMonths)
: undefined,
monthlySalesAmount: wonToMan(store.sale?.monthlySalesAmount),
monthlyProfitAmount: wonToMan(store.sale?.monthlyProfitAmount),
startupCostAmount: wonToMan(store.sale?.startupCostAmount),
listingDescription: store.sale?.listingDescription ?? '',
locationHighlight: store.sale?.locationHighlight ?? '',
saleReason: store.sale?.saleReason ?? '',
exclusiveAreaSqm:
store.facility?.exclusiveAreaSqm != null ? String(store.facility.exclusiveAreaSqm) : undefined,
floorLevel:
store.facility?.floorLevel != null ? String(store.facility.floorLevel) : undefined,
kitchenEquipmentSummary: store.facility?.kitchenEquipmentSummary ?? '',
};
}
export async function updateStoreDraftAction(
publicId: string,
_prevState: StoreEditFormState,
formData: FormData,
): Promise<StoreEditFormState> {
const fieldValues: Record<string, string | undefined> = {};
const get = (key: string): string | null => {
const v = formData.get(key);
if (v == null) return null;
const s = String(v).trim();
fieldValues[key] = s;
return s === '' ? null : s;
};
const listingTitle = get('listingTitle') ?? '';
const regionClusterCode = get('regionClusterCode') ?? '';
const industryLeafCode = get('industryLeafCode') ?? '';
const roadAddress = get('roadAddress') ?? '';
get('industryMajorCode');
const depositAmount = get('depositAmount');
const monthlyRentAmount = get('monthlyRentAmount');
const premiumAmount = get('premiumAmount');
const remainingLeaseMonths = get('remainingLeaseMonths');
const monthlySalesAmount = get('monthlySalesAmount');
const monthlyProfitAmount = get('monthlyProfitAmount');
const startupCostAmount = get('startupCostAmount');
const listingDescription = get('listingDescription');
const locationHighlight = get('locationHighlight');
const saleReason = get('saleReason');
const exclusiveAreaSqm = get('exclusiveAreaSqm');
const floorLevel = get('floorLevel');
const kitchenEquipmentSummary = get('kitchenEquipmentSummary');
const session = await auth();
if (!session?.user?.dbId) {
return { error: '로그인이 필요합니다.', fieldValues };
}
if (!listingTitle || !regionClusterCode || !industryLeafCode || !roadAddress) {
return { error: '필수 항목이 누락되었습니다.', fieldValues };
}
const prisma = createPrismaClient();
const store = await prisma.store.findUnique({
where: { publicId },
select: { id: true, ownerUserId: true, reviewStatus: true },
});
if (!store) return { error: '매장을 찾을 수 없습니다.', fieldValues };
if (store.ownerUserId !== BigInt(session.user.dbId)) {
return { error: '권한이 없습니다.', fieldValues };
}
if (store.reviewStatus !== 'DRAFT' && store.reviewStatus !== 'REJECTED') {
return { error: '검토 중이거나 게시된 매장은 수정할 수 없습니다.', fieldValues };
}
const region = await prisma.regionHierarchy.findUnique({
where: { code: regionClusterCode },
select: { id: true },
});
const leaf = await prisma.industryTaxonomy.findUnique({
where: { code: industryLeafCode },
select: { id: true },
});
if (!region || !leaf) return { error: '지역/업종 코드가 올바르지 않습니다.', fieldValues };
await prisma.$transaction(async (tx) => {
await tx.store.update({
where: { id: store.id },
data: {
listingTitle,
roadAddress,
regionClusterId: region.id,
industryLeafId: leaf.id,
},
});
// 임대 정보
await tx.storeLease.upsert({
where: { storeId: store.id },
create: {
storeId: store.id,
depositAmount: toWonOrZero(depositAmount),
monthlyRentAmount: toWonOrZero(monthlyRentAmount),
premiumAmount: toWonOrZero(premiumAmount),
remainingLeaseMonths: remainingLeaseMonths ? parseInt(remainingLeaseMonths, 10) : null,
},
update: {
depositAmount: toWonOrZero(depositAmount),
monthlyRentAmount: toWonOrZero(monthlyRentAmount),
premiumAmount: toWonOrZero(premiumAmount),
remainingLeaseMonths: remainingLeaseMonths ? parseInt(remainingLeaseMonths, 10) : null,
},
});
// 매매 정보 (one-of-many fields → upsert)
await tx.storeSale.upsert({
where: { storeId: store.id },
create: {
storeId: store.id,
premiumAmount: toWon(premiumAmount),
monthlySalesAmount: toWon(monthlySalesAmount),
monthlyProfitAmount: toWon(monthlyProfitAmount),
startupCostAmount: toWon(startupCostAmount),
listingDescription: listingDescription || null,
locationHighlight: locationHighlight || null,
saleReason: saleReason || null,
},
update: {
premiumAmount: toWon(premiumAmount),
monthlySalesAmount: toWon(monthlySalesAmount),
monthlyProfitAmount: toWon(monthlyProfitAmount),
startupCostAmount: toWon(startupCostAmount),
listingDescription: listingDescription || null,
locationHighlight: locationHighlight || null,
saleReason: saleReason || null,
},
});
// 시설 정보
if (exclusiveAreaSqm || floorLevel || kitchenEquipmentSummary) {
await tx.storeFacility.upsert({
where: { storeId: store.id },
create: {
storeId: store.id,
exclusiveAreaSqm: exclusiveAreaSqm ? Number(exclusiveAreaSqm) : 0,
seatCount: 0,
floorLevel: floorLevel ? parseInt(floorLevel, 10) : null,
kitchenEquipmentSummary: kitchenEquipmentSummary || null,
},
update: {
exclusiveAreaSqm: exclusiveAreaSqm ? Number(exclusiveAreaSqm) : 0,
floorLevel: floorLevel ? parseInt(floorLevel, 10) : null,
kitchenEquipmentSummary: kitchenEquipmentSummary || null,
},
});
}
});
revalidatePath(`/stores/${publicId}`);
redirect(`/stores/${publicId}`);
}
@@ -0,0 +1,318 @@
'use client';
import { useActionState, useMemo, useState } from 'react';
import Link from 'next/link';
import { updateStoreDraftAction, type StoreEditFormState } from './actions';
import { INDUSTRY_MAJORS } from '../../industries';
type Initial = {
publicId: string;
listingTitle: string;
regionClusterCode: string;
industryMajorCode: string;
industryLeafCode: string;
roadAddress: string;
depositAmount?: string;
monthlyRentAmount?: string;
premiumAmount?: string;
remainingLeaseMonths?: string;
monthlySalesAmount?: string;
monthlyProfitAmount?: string;
startupCostAmount?: string;
listingDescription?: string;
locationHighlight?: string;
saleReason?: string;
exclusiveAreaSqm?: string;
floorLevel?: string;
kitchenEquipmentSummary?: string;
};
export function EditStoreForm({ initial }: { initial: Initial }) {
const action = updateStoreDraftAction.bind(null, initial.publicId);
const initialState: StoreEditFormState = { fieldValues: initial as Record<string, string | undefined> };
const [state, formAction, isPending] = useActionState(action, initialState);
const [majorCode, setMajorCode] = useState<string>(
state.fieldValues?.industryMajorCode ?? initial.industryMajorCode ?? '',
);
const leafOptions = useMemo(
() => INDUSTRY_MAJORS.find((m) => m.code === majorCode)?.children ?? [],
[majorCode],
);
const fv = state.fieldValues ?? (initial as Record<string, string | undefined>);
return (
<main className="mx-auto max-w-3xl px-6 py-10 font-body">
<div className="mb-6">
<Link href={`/stores/${initial.publicId}`} className="text-sm text-warm-600 hover:text-warm-700">
</Link>
</div>
<div className="animate-fade-up">
<h1 className="font-display text-3xl font-bold text-ink"> </h1>
<p className="mt-1 text-sm text-ink-muted">
. .
</p>
</div>
{state.error && (
<div className="mt-4 rounded-2xl border border-red-200/60 bg-red-50 px-5 py-4 text-sm text-red-700">
{state.error}
</div>
)}
<form action={formAction} className="mt-8 space-y-8 animate-fade-up">
{/* 기본 정보 */}
<section>
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div>
<label className="block text-sm text-ink-muted mb-1"> *</label>
<input
type="text"
name="listingTitle"
required
defaultValue={fv.listingTitle}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<div>
<label className="block text-sm text-ink-muted mb-1"> *</label>
<select
name="regionClusterCode"
required
defaultValue={fv.regionClusterCode}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
>
<option value=""> </option>
<option value="KR.BETA.GANGNAM_CORE"> (//)</option>
<option value="KR.BETA.MAPO_CORE"> (//)</option>
</select>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> *</label>
<select
name="industryMajorCode"
required
value={majorCode}
onChange={(e) => setMajorCode(e.target.value)}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
>
<option value=""> </option>
{INDUSTRY_MAJORS.map((m) => (
<option key={m.code} value={m.code}>
{m.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> *</label>
<select
name="industryLeafCode"
required
defaultValue={fv.industryLeafCode}
disabled={!majorCode}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none disabled:opacity-50"
>
<option value=""> </option>
{leafOptions.map((c) => (
<option key={c.code} value={c.code}>
{c.label}
</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> *</label>
<input
type="text"
name="roadAddress"
required
defaultValue={fv.roadAddress}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
</section>
{/* 매매 정보 */}
<section>
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="premiumAmount"
placeholder="12000"
defaultValue={fv.premiumAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="startupCostAmount"
placeholder="15000"
defaultValue={fv.startupCostAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlySalesAmount"
placeholder="8500"
defaultValue={fv.monthlySalesAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlyProfitAmount"
placeholder="990"
defaultValue={fv.monthlyProfitAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> </label>
<textarea
rows={3}
name="listingDescription"
defaultValue={fv.listingDescription}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> </label>
<textarea
rows={3}
name="locationHighlight"
defaultValue={fv.locationHighlight}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> </label>
<textarea
rows={3}
name="saleReason"
defaultValue={fv.saleReason}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
</section>
{/* 임대 정보 */}
<section>
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="depositAmount"
placeholder="5000"
defaultValue={fv.depositAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlyRentAmount"
placeholder="300"
defaultValue={fv.monthlyRentAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="remainingLeaseMonths"
defaultValue={fv.remainingLeaseMonths}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
</section>
{/* 시설 정보 */}
<section>
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="exclusiveAreaSqm"
step="0.01"
defaultValue={fv.exclusiveAreaSqm}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"></label>
<input
type="number"
name="floorLevel"
defaultValue={fv.floorLevel}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> </label>
<textarea
rows={3}
name="kitchenEquipmentSummary"
defaultValue={fv.kitchenEquipmentSummary}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
</div>
</section>
<div className="flex gap-3">
<button
type="submit"
disabled={isPending}
className="rounded-full px-6 py-3 text-sm font-semibold text-white disabled:opacity-50"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 8px 20px -8px rgba(3,199,90,0.45)',
}}
>
{isPending ? '저장 중...' : '저장'}
</button>
<Link
href={`/stores/${initial.publicId}`}
className="rounded-full border-2 border-ink/15 px-6 py-3 text-sm font-medium text-ink hover:border-ink/40 transition-colors"
>
</Link>
</div>
</form>
</main>
);
}
@@ -0,0 +1,12 @@
import { notFound } from 'next/navigation';
import { getStoreForEdit } from './actions';
import { EditStoreForm } from './edit-form';
export const dynamic = 'force-dynamic';
export default async function EditStorePage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const initial = await getStoreForEdit(id);
if (!initial) notFound();
return <EditStoreForm initial={initial} />;
}
+321 -273
View File
@@ -46,7 +46,14 @@ async function handleDeleteDraft(formData: FormData) {
function formatKRW(value: number | null | undefined): string {
if (value == null) return '-';
return `${Number(value).toLocaleString('ko-KR')}`;
const won = Number(value);
if (won === 0) return '0원';
const eok = Math.floor(won / 100_000_000);
const man = Math.floor((won % 100_000_000) / 10_000);
if (eok > 0 && man > 0) return `${eok}${man.toLocaleString('ko-KR')}만원`;
if (eok > 0) return `${eok}억원`;
if (man > 0) return `${man.toLocaleString('ko-KR')}만원`;
return `${won.toLocaleString('ko-KR')}`;
}
function formatMargin(sales?: number | null, profit?: number | null): string {
@@ -62,45 +69,46 @@ function formatPaybackMonths(premium?: number | null, profit?: number | null): s
return `${months.toFixed(1)}개월`;
}
const STATUS_META: Record<string, { label: string; bg: string; fg: string }> = {
OPEN: { label: '거래 가능', bg: '#03C75A', fg: '#ffffff' },
MATCHING: { label: '매칭 중', bg: '#00C4A7', fg: '#ffffff' },
RESERVED: { label: '예약', bg: '#84E1A1', fg: '#0F1D17' },
CONTRACTED: { label: '계약 진행 중', bg: '#0AB070', fg: '#ffffff' },
CLOSED: { label: '거래 완료', bg: '#7B8581', fg: '#ffffff' },
CANCELLED: { label: '취소', bg: '#E4E8E6', fg: '#0F1D17' },
};
export default async function StoreDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const prisma = createPrismaClient();
const store = await prisma.store.findUnique({
where: { publicId: id },
include: {
regionCluster: { select: { nameKo: true } },
industryLeaf: {
select: { nameKo: true, parent: { select: { nameKo: true } } },
const [store, session] = await Promise.all([
prisma.store.findUnique({
where: { publicId: id },
include: {
regionCluster: { select: { nameKo: true } },
industryLeaf: {
select: { nameKo: true, parent: { select: { nameKo: true } } },
},
lease: true,
sale: true,
facility: true,
photos: {
orderBy: { sortOrder: 'asc' },
},
},
lease: true,
sale: true,
facility: true,
photos: {
orderBy: { sortOrder: 'asc' },
},
},
});
}),
auth(),
]);
if (!store) {
notFound();
}
const statusLabel =
store.dealStatus === 'OPEN'
? '거래 가능'
: store.dealStatus === 'MATCHING'
? '매칭 중'
: store.dealStatus === 'CONTRACTED'
? '계약 진행 중'
: store.dealStatus;
const isOwner = !!session?.user?.dbId && store.ownerUserId === BigInt(session.user.dbId);
const canEdit = isOwner && (store.reviewStatus === 'DRAFT' || store.reviewStatus === 'REJECTED');
const statusClass =
store.dealStatus === 'OPEN'
? 'bg-sage-500/10 text-sage-600'
: store.dealStatus === 'MATCHING'
? 'bg-warm-400/15 text-warm-700'
: 'bg-ink/5 text-ink-muted';
const status = STATUS_META[store.dealStatus] ?? { label: store.dealStatus, bg: '#E4E8E6', fg: '#0F1D17' };
const industryLabel = [
store.industryLeaf?.parent?.nameKo,
@@ -114,281 +122,321 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
const profit = store.sale?.monthlyProfitAmount ?? null;
const startup = store.sale?.startupCostAmount ?? null;
return (
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
<div className="mb-6">
<Link href="/stores" className="text-sm text-warm-600 hover:text-warm-700">
</Link>
</div>
const heroPhoto = store.photos.find((p) => p.isRepresentative) ?? store.photos[0] ?? null;
const restPhotos = store.photos.filter((p) => p.id !== heroPhoto?.id).slice(0, 8);
<div className="animate-fade-up rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-8">
<div className="flex items-start justify-between">
<div>
<h1 className="font-display text-3xl font-bold text-ink">{store.listingTitle}</h1>
<p className="mt-1 text-sm text-ink-muted"> ID: {store.publicId}</p>
</div>
<span className={`rounded-full px-3 py-1 text-sm font-medium ${statusClass}`}>
{statusLabel}
return (
<main className="mx-auto max-w-5xl pb-12 font-body">
{/* ========================================================== *
* PHOTO HERO (mobile first — 직방/다방 style)
* ========================================================== */}
<section className="relative">
<div className="photo-hero">
{heroPhoto ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={heroPhoto.storageKey}
alt={store.listingTitle}
className="h-full w-full object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center text-sm text-ink-muted">
</div>
)}
<span
className="absolute left-4 top-4 rounded-full px-3 py-1 text-xs font-semibold shadow-[0_6px_14px_-6px_rgba(15,29,23,0.25)]"
style={{ background: status.bg, color: status.fg }}
>
{status.label}
</span>
<Link
href="/stores"
className="absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full bg-white/90 text-ink shadow-[0_6px_14px_-6px_rgba(15,29,23,0.3)] backdrop-blur"
aria-label="목록으로"
>
×
</Link>
{store.photos.length > 0 && (
<span className="absolute bottom-4 right-4 inline-flex items-center gap-1 rounded-full bg-black/55 px-3 py-1 text-xs font-semibold text-white">
📷 {store.photos.length}
</span>
)}
</div>
{/* 기본 정보 */}
<section className="mt-8">
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="grid grid-cols-2 gap-4">
<InfoItem label="지역" value={store.regionCluster?.nameKo ?? '-'} />
<InfoItem label="업종" value={industryLabel || '-'} />
<InfoItem label="주소" value={store.roadAddress} />
<InfoItem label="등록일" value={store.createdAt.toLocaleDateString('ko-KR')} />
</div>
</section>
{/* 매매 정보 */}
<section className="mt-8 rounded-2xl border border-warm-300/40 bg-warm-50/70 p-6">
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
{/* 핵심 지표 (첫번째 캡쳐) */}
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
<BigInfoItem label="권리금" value={formatKRW(premium != null ? Number(premium) : null)} accent />
<BigInfoItem label="창업비용" value={formatKRW(startup != null ? Number(startup) : null)} />
<BigInfoItem label="월매출" value={formatKRW(sales != null ? Number(sales) : null)} />
<BigInfoItem label="월수익" value={formatKRW(profit != null ? Number(profit) : null)} accent />
<BigInfoItem
label="월수익률"
value={formatMargin(
sales != null ? Number(sales) : undefined,
profit != null ? Number(profit) : undefined,
)}
/>
<BigInfoItem
label="권리금 회수기간"
value={formatPaybackMonths(
premium != null ? Number(premium) : undefined,
profit != null ? Number(profit) : undefined,
)}
/>
</div>
{/* 매물설명 + 매장사진 (두번째 캡쳐) */}
{(store.sale?.listingDescription || store.photos.length > 0) && (
<div className="mt-8">
<h3 className="font-display text-base font-bold text-ink mb-2"> </h3>
{store.sale?.listingDescription ? (
<p className="text-sm leading-relaxed text-ink whitespace-pre-line">
{store.sale.listingDescription}
</p>
) : (
<p className="text-sm text-ink-muted"> </p>
)}
{store.photos[0] && (
<div className="mt-4 overflow-hidden rounded-xl bg-warm-100">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={store.photos[0].storageKey}
alt="매장 대표 사진"
className="aspect-[16/10] w-full object-cover"
/>
</div>
)}
</div>
)}
{/* 매출/월수익 */}
{(sales != null || profit != null) && (
<div className="mt-8">
<h3 className="font-display text-base font-bold text-ink mb-3"> / </h3>
<div className="rounded-xl bg-white/70 p-4">
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
<InfoItem
label="월매출"
value={formatKRW(sales != null ? Number(sales) : null)}
/>
<InfoItem
label="월수익"
value={formatKRW(profit != null ? Number(profit) : null)}
/>
<InfoItem
label="월수익률"
value={formatMargin(
sales != null ? Number(sales) : undefined,
profit != null ? Number(profit) : undefined,
)}
/>
</div>
{/* Thumbnail strip (photos 2..9) */}
{restPhotos.length > 0 && (
<div className="mt-3 flex gap-2 overflow-x-auto px-4 pb-1 md:px-0">
{restPhotos.map((photo) => (
<div
key={photo.id.toString()}
className="h-20 w-28 shrink-0 overflow-hidden rounded-xl"
style={{ background: '#EFF2F0' }}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={photo.storageKey}
alt="매장 사진"
className="h-full w-full object-cover"
/>
</div>
</div>
))}
</div>
)}
</section>
{/* ========================================================== *
* SUMMARY BAR (price, status, title)
* ========================================================== */}
<section className="px-5 pt-5 md:px-0">
<p className="text-xs text-ink-muted">
{store.regionCluster?.nameKo ?? '-'}
{industryLabel ? ` · ${industryLabel}` : ''}
</p>
<h1 className="mt-1 font-display text-2xl font-extrabold tracking-tight text-ink md:text-3xl">
{store.listingTitle}
</h1>
<div className="mt-5 grid grid-cols-2 gap-3 md:grid-cols-4">
<KpiTile label="권리금" value={formatKRW(premium != null ? Number(premium) : null)} accent />
<KpiTile label="월수익" value={formatKRW(profit != null ? Number(profit) : null)} accent />
<KpiTile label="월매출" value={formatKRW(sales != null ? Number(sales) : null)} />
<KpiTile
label="회수기간"
value={formatPaybackMonths(
premium != null ? Number(premium) : undefined,
profit != null ? Number(profit) : undefined,
)}
/>
</div>
</section>
{/* ========================================================== *
* BODY
* ========================================================== */}
<section className="mt-6 grid gap-4 px-5 md:grid-cols-3 md:gap-6 md:px-0">
<div className="md:col-span-2 space-y-6">
{/* 매물 설명 */}
{store.sale?.listingDescription && (
<SectionCard title="매물 설명">
<p className="text-sm leading-relaxed text-ink whitespace-pre-line">
{store.sale.listingDescription}
</p>
</SectionCard>
)}
{/* 입지특징 */}
{/* 입지 특징 */}
{store.sale?.locationHighlight && (
<div className="mt-6">
<h3 className="font-display text-base font-bold text-ink mb-2"> </h3>
<p className="rounded-xl bg-white/70 p-4 text-sm text-ink whitespace-pre-line">
<SectionCard title="입지 특징">
<p className="text-sm leading-relaxed text-ink whitespace-pre-line">
{store.sale.locationHighlight}
</p>
</div>
</SectionCard>
)}
{/* 매매사유 */}
{/* 매매 사유 */}
{store.sale?.saleReason && (
<div className="mt-6">
<h3 className="font-display text-base font-bold text-ink mb-2"> </h3>
<p className="rounded-xl bg-white/70 p-4 text-sm text-ink whitespace-pre-line">
<SectionCard title="매매 사유">
<p className="text-sm leading-relaxed text-ink whitespace-pre-line">
{store.sale.saleReason}
</p>
</div>
</SectionCard>
)}
{/* 현장사진 (대표 사진 제외한 나머지) */}
{store.photos.length > 1 && (
<div className="mt-8">
<h3 className="font-display text-base font-bold text-ink mb-3"> </h3>
<div className="grid grid-cols-3 gap-2 md:grid-cols-4">
{store.photos.slice(1).map((photo) => (
<div
key={photo.id.toString()}
className="aspect-square overflow-hidden rounded-lg bg-warm-100"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={photo.storageKey}
alt="현장 사진"
className="h-full w-full object-cover"
/>
</div>
))}
{/* 시설 정보 */}
<SectionCard title="시설 정보">
<div className="grid grid-cols-2 gap-4">
<InfoItem
label="면적"
value={
store.facility?.exclusiveAreaSqm != null
? `${Number(store.facility.exclusiveAreaSqm).toLocaleString('ko-KR')}`
: '-'
}
/>
<InfoItem
label="층수"
value={store.facility?.floorLevel != null ? `${store.facility.floorLevel}` : '-'}
/>
</div>
{store.facility?.kitchenEquipmentSummary && (
<div className="mt-4">
<p className="text-xs text-ink-muted"> </p>
<p className="mt-1 text-sm text-ink">{store.facility.kitchenEquipmentSummary}</p>
</div>
)}
</SectionCard>
</div>
{/* Sticky sidebar */}
<aside className="md:col-span-1 md:sticky md:top-24 md:self-start">
<div
className="rounded-2xl border bg-white p-5 shadow-[0_2px_14px_-10px_rgba(15,29,23,0.15)]"
style={{ borderColor: '#E4E8E6' }}
>
<p className="text-xs text-ink-muted"> ID</p>
<p className="mt-0.5 font-mono text-xs text-ink">{store.publicId}</p>
<div className="mt-5 space-y-3">
<RowInfo label="지역" value={store.regionCluster?.nameKo ?? '-'} />
<RowInfo label="업종" value={industryLabel || '-'} />
<RowInfo label="주소" value={store.roadAddress} />
<RowInfo label="등록" value={store.createdAt.toLocaleDateString('ko-KR')} />
</div>
{/* 임대 정보 */}
<div className="mt-5 border-t pt-5" style={{ borderColor: '#EFF2F0' }}>
<p className="font-display text-sm font-bold text-ink"> </p>
<div className="mt-3 space-y-2">
<RowInfo
label="보증금"
value={formatKRW(store.lease?.depositAmount != null ? Number(store.lease.depositAmount) : null)}
/>
<RowInfo
label="월세"
value={formatKRW(store.lease?.monthlyRentAmount != null ? Number(store.lease.monthlyRentAmount) : null)}
/>
<RowInfo
label="잔여 계약"
value={
store.lease?.remainingLeaseMonths != null
? `${store.lease.remainingLeaseMonths}개월`
: '-'
}
/>
<RowInfo label="창업비용" value={formatKRW(startup != null ? Number(startup) : null)} />
<RowInfo
label="월수익률"
value={formatMargin(
sales != null ? Number(sales) : undefined,
profit != null ? Number(profit) : undefined,
)}
/>
</div>
</div>
)}
</section>
{/* 임대 정보 */}
<section className="mt-8">
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="grid grid-cols-2 gap-4">
<InfoItem
label="보증금"
value={formatKRW(store.lease?.depositAmount != null ? Number(store.lease.depositAmount) : null)}
/>
<InfoItem
label="월세"
value={formatKRW(store.lease?.monthlyRentAmount != null ? Number(store.lease.monthlyRentAmount) : null)}
/>
<InfoItem
label="임대 잔여 기간"
value={
store.lease?.remainingLeaseMonths != null
? `${store.lease.remainingLeaseMonths}개월`
: '-'
}
/>
</div>
</section>
{/* 시설 정보 */}
<section className="mt-8">
<h2 className="font-display text-xl font-bold text-ink mb-4"> </h2>
<div className="grid grid-cols-2 gap-4">
<InfoItem
label="면적"
value={
store.facility?.exclusiveAreaSqm != null
? `${Number(store.facility.exclusiveAreaSqm).toLocaleString('ko-KR')}`
: '-'
}
/>
<InfoItem
label="층수"
value={store.facility?.floorLevel != null ? `${store.facility.floorLevel}` : '-'}
/>
</div>
{store.facility?.kitchenEquipmentSummary && (
<div className="mt-4">
<p className="text-sm text-ink-muted"> </p>
<p className="mt-1 text-sm text-ink">{store.facility.kitchenEquipmentSummary}</p>
{/* 액션 버튼 */}
<div className="mt-6 space-y-3">
{canEdit && (
<Link
href={`/stores/${store.publicId}/edit`}
className="block w-full rounded-full border-2 py-3 text-center text-sm font-semibold text-ink hover:bg-[#F7FAF8]"
style={{ borderColor: '#03C75A', color: '#02A149' }}
>
</Link>
)}
{store.reviewStatus === 'DRAFT' && isOwner && (
<>
<form action={handleSubmitForReview}>
<input type="hidden" name="storePublicId" value={store.publicId} />
<button
type="submit"
className="w-full rounded-full py-3 text-sm font-semibold text-white"
style={{ background: 'linear-gradient(135deg,#03C75A,#02A149)' }}
>
</button>
</form>
<form action={handleDeleteDraft}>
<input type="hidden" name="storePublicId" value={store.publicId} />
<button
type="submit"
className="w-full rounded-full border-2 border-red-300 py-3 text-sm text-red-600 hover:bg-red-50"
>
</button>
</form>
</>
)}
{store.publicationStatus === 'PUBLISHED' && store.dealStatus !== 'CLOSED' && (
<>
<Link
href={`/matching?storeId=${id}`}
className="block w-full rounded-full py-3 text-center text-sm font-semibold text-white"
style={{
background: 'linear-gradient(135deg,#03C75A,#02A149)',
boxShadow: '0 10px 24px -10px rgba(3,199,90,0.55)',
}}
>
</Link>
<Link
href={`/subsidies?storeId=${id}`}
className="block w-full rounded-full border-2 py-3 text-center text-sm font-semibold text-ink"
style={{ borderColor: '#E4E8E6' }}
>
</Link>
</>
)}
{store.dealStatus === 'CLOSED' && (
<p className="rounded-xl border px-3 py-3 text-center text-sm text-ink-muted" style={{ borderColor: '#E4E8E6', background: '#F7FAF8' }}>
</p>
)}
{store.reviewStatus === 'SUBMITTED' && (
<p className="text-sm text-ink-muted"> </p>
)}
{store.reviewStatus === 'REJECTED' && (
<p className="text-sm text-red-600">. .</p>
)}
</div>
)}
</section>
</div>
{/* 액션 버튼 */}
<div className="mt-10 flex flex-wrap gap-3 border-t border-ink/5 pt-6">
{store.reviewStatus === 'DRAFT' && (
<>
<form action={handleSubmitForReview}>
<input type="hidden" name="storePublicId" value={store.publicId} />
<button
type="submit"
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
>
</button>
</form>
<form action={handleDeleteDraft}>
<input type="hidden" name="storePublicId" value={store.publicId} />
<button
type="submit"
className="rounded-full border-2 border-red-300 px-6 py-3 text-sm text-red-600 hover:bg-red-50"
>
</button>
</form>
</>
)}
{store.publicationStatus === 'PUBLISHED' && (
<>
<Link
href={`/matching?storeId=${id}`}
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
>
</Link>
<Link
href={`/subsidies?storeId=${id}`}
className="rounded-full border-2 border-ink/15 px-6 py-3 text-sm font-medium text-ink hover:border-ink/40 transition-colors"
>
</Link>
</>
)}
{store.reviewStatus === 'SUBMITTED' && (
<p className="text-sm text-ink-muted"> </p>
)}
{store.reviewStatus === 'REJECTED' && (
<p className="text-sm text-red-600">. .</p>
)}
</div>
</div>
<p className="mt-6 rounded-2xl bg-warm-100/60 px-5 py-4 text-xs leading-relaxed text-ink-muted">
주의: 창업에이전트는 ,
. · , .
, .
.
</p>
<p className="mt-4 rounded-xl border px-4 py-3 text-[11px] leading-relaxed text-ink-muted" style={{ borderColor: '#EFF2F0', background: '#F7FAF8' }}>
,
. · .
</p>
</aside>
</section>
</main>
);
}
function InfoItem({ label, value }: { label: string; value: string }) {
function KpiTile({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
return (
<div>
<p className="text-sm text-ink-muted">{label}</p>
<p className="mt-0.5 text-sm font-medium text-ink">{value}</p>
</div>
);
}
function BigInfoItem({ label, value, accent }: { label: string; value: string; accent?: boolean }) {
return (
<div>
<p className="text-sm text-ink-muted">{label}</p>
<div
className="rounded-2xl border bg-white p-4 shadow-[0_2px_10px_-8px_rgba(15,29,23,0.15)]"
style={{ borderColor: '#E4E8E6' }}
>
<p className="text-[11px] font-semibold tracking-wider uppercase text-ink-muted">{label}</p>
<p
className={`mt-1 font-display text-lg font-bold ${accent ? 'text-warm-700' : 'text-ink'}`}
className={`mt-1 font-display text-lg font-extrabold tracking-tight ${accent ? '' : 'text-ink'}`}
style={accent ? { color: '#02A149' } : undefined}
>
{value}
</p>
</div>
);
}
function SectionCard({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section
className="rounded-2xl border bg-white p-5 shadow-[0_2px_10px_-8px_rgba(15,29,23,0.1)]"
style={{ borderColor: '#E4E8E6' }}
>
<h3 className="font-display text-base font-bold text-ink">{title}</h3>
<div className="mt-3">{children}</div>
</section>
);
}
function InfoItem({ label, value }: { label: string; value: string }) {
return (
<div>
<p className="text-xs text-ink-muted">{label}</p>
<p className="mt-0.5 text-sm font-medium text-ink">{value}</p>
</div>
);
}
function RowInfo({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-center justify-between text-sm">
<span className="text-xs text-ink-muted">{label}</span>
<span className="font-medium text-ink">{value}</span>
</div>
);
}
+12 -7
View File
@@ -91,6 +91,11 @@ export async function createStoreDraftAction(
locationHighlight ||
saleReason;
// 폼 입력은 만원 단위 → DB는 원 단위로 저장 (× 10000)
const MAN = 10000;
const toWon = (v: string | null) => (v ? Number(v) * MAN : undefined);
const toWonRequired = (v: string | null) => (v ? Number(v) * MAN : 0);
const input: CreateStoreDraftInput = {
ownerUserId: session.user.dbId,
listingTitle,
@@ -100,9 +105,9 @@ export async function createStoreDraftAction(
...(depositAmount || monthlyRentAmount || premiumAmount || remainingLeaseMonths
? {
lease: {
depositAmount: depositAmount ? Number(depositAmount) : 0,
monthlyRentAmount: monthlyRentAmount ? Number(monthlyRentAmount) : 0,
premiumAmount: premiumAmount ? Number(premiumAmount) : 0,
depositAmount: toWonRequired(depositAmount),
monthlyRentAmount: toWonRequired(monthlyRentAmount),
premiumAmount: toWonRequired(premiumAmount),
remainingLeaseMonths: remainingLeaseMonths
? parseInt(remainingLeaseMonths, 10)
: undefined,
@@ -112,10 +117,10 @@ export async function createStoreDraftAction(
...(hasSale
? {
sale: {
premiumAmount: premiumAmount ? Number(premiumAmount) : undefined,
monthlySalesAmount: monthlySalesAmount ? Number(monthlySalesAmount) : undefined,
monthlyProfitAmount: monthlyProfitAmount ? Number(monthlyProfitAmount) : undefined,
startupCostAmount: startupCostAmount ? Number(startupCostAmount) : undefined,
premiumAmount: toWon(premiumAmount),
monthlySalesAmount: toWon(monthlySalesAmount),
monthlyProfitAmount: toWon(monthlyProfitAmount),
startupCostAmount: toWon(startupCostAmount),
listingDescription: listingDescription || undefined,
locationHighlight: locationHighlight || undefined,
saleReason: saleReason || undefined,
+12 -12
View File
@@ -124,21 +124,21 @@ export default function NewStorePage() {
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="premiumAmount"
placeholder="120000000"
placeholder="12000"
defaultValue={state.fieldValues?.premiumAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="startupCostAmount"
placeholder="150000000"
placeholder="15000"
defaultValue={state.fieldValues?.startupCostAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
@@ -146,21 +146,21 @@ export default function NewStorePage() {
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlySalesAmount"
placeholder="85000000"
placeholder="8500"
defaultValue={state.fieldValues?.monthlySalesAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlyProfitAmount"
placeholder="9900000"
placeholder="990"
defaultValue={state.fieldValues?.monthlyProfitAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
@@ -205,21 +205,21 @@ export default function NewStorePage() {
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="depositAmount"
placeholder="50000000"
placeholder="5000"
defaultValue={state.fieldValues?.depositAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<label className="block text-sm text-ink-muted mb-1"> ()</label>
<input
type="number"
name="monthlyRentAmount"
placeholder="3000000"
placeholder="300"
defaultValue={state.fieldValues?.monthlyRentAmount}
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
/>
+44 -29
View File
@@ -10,15 +10,24 @@ function formatKRWShort(value: number | null | undefined): string {
const v = Number(value);
if (v >= 100_000_000) {
const eok = v / 100_000_000;
return `${Number.isInteger(eok) ? eok : eok.toFixed(1)}`;
return `${Number.isInteger(eok) ? eok : eok.toFixed(1)}`;
}
if (v >= 10_000) {
const man = Math.round(v / 10_000);
return `${man.toLocaleString('ko-KR')}`;
return `${man.toLocaleString('ko-KR')}`;
}
return `${v.toLocaleString('ko-KR')}`;
}
const STATUS_META: Record<string, { label: string; bg: string; fg: string }> = {
OPEN: { label: '거래 가능', bg: '#03C75A', fg: '#ffffff' },
MATCHING: { label: '매칭 중', bg: '#00C4A7', fg: '#ffffff' },
RESERVED: { label: '예약', bg: '#84E1A1', fg: '#0F1D17' },
CONTRACTED: { label: '계약 진행 중', bg: '#0AB070', fg: '#ffffff' },
CLOSED: { label: '거래 완료', bg: '#7B8581', fg: '#ffffff' },
CANCELLED: { label: '취소', bg: '#E4E8E6', fg: '#0F1D17' },
};
export default async function StoresPage({
searchParams,
}: {
@@ -68,18 +77,25 @@ export default async function StoresPage({
return (
<main className="mx-auto max-w-7xl px-6 py-10 font-body">
<div className="animate-fade-up flex items-center justify-between">
<div className="animate-fade-up flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="font-display text-3xl font-bold text-ink"> </h1>
<p className="font-mono text-[11px] tracking-[0.25em] uppercase text-ink-muted">
<span className="text-grad-primary"></span> Store · Marketplace
</p>
<h1 className="mt-2 font-display text-3xl font-black tracking-tight text-ink"> </h1>
<p className="mt-1 text-sm text-ink-muted">
{stores.length.toLocaleString('ko-KR')} ·
</p>
</div>
<Link
href="/stores/new"
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
className="rounded-full px-6 py-3 text-sm font-semibold text-white transition-transform hover:-translate-y-0.5"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 55%,#018f40 100%)',
boxShadow: '0 10px 24px -10px rgba(3,199,90,0.5)',
}}
>
</Link>
</div>
@@ -90,12 +106,13 @@ export default async function StoresPage({
<div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 animate-fade-up">
{stores.length === 0 ? (
<div className="col-span-full py-16 text-center text-sm text-ink-muted">
</div>
) : (
stores.map((store) => {
const premium = store.sale?.premiumAmount ?? store.lease?.premiumAmount ?? null;
const profit = store.sale?.monthlyProfitAmount ?? null;
const sales = store.sale?.monthlySalesAmount ?? null;
const industryLabel = [
store.industryLeaf?.parent?.nameKo,
store.industryLeaf?.nameKo,
@@ -103,21 +120,23 @@ export default async function StoresPage({
.filter(Boolean)
.join('/');
const photoSrc = store.photos[0]?.storageKey ?? null;
const meta = STATUS_META[store.dealStatus] ?? { label: store.dealStatus, bg: '#E4E8E6', fg: '#0F1D17' };
const isClosed = store.dealStatus === 'CLOSED';
return (
<Link
key={store.publicId}
href={`/stores/${store.publicId}`}
className="block overflow-hidden rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm card-lift"
className={`block overflow-hidden rounded-2xl border bg-white card-lift ${isClosed ? 'opacity-80' : ''}`}
style={{ borderColor: '#E4E8E6' }}
>
{/* 사진 영역 */}
<div className="relative aspect-[4/3] w-full bg-warm-100">
<div className="relative aspect-[4/3] w-full" style={{ background: '#EFF2F0' }}>
{photoSrc ? (
// eslint-disable-next-line @next/next/no-img-element
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={photoSrc}
alt={store.listingTitle}
className="h-full w-full object-cover"
className={`h-full w-full object-cover ${isClosed ? 'grayscale' : ''}`}
/>
) : (
<div className="flex h-full w-full items-center justify-center text-xs text-ink-muted">
@@ -125,23 +144,13 @@ export default async function StoresPage({
</div>
)}
<span
className={`absolute right-3 top-3 rounded-full px-2 py-0.5 text-xs font-medium ${
store.dealStatus === 'OPEN'
? 'bg-sage-500/90 text-white'
: store.dealStatus === 'MATCHING'
? 'bg-warm-600/90 text-white'
: 'bg-ink/70 text-white'
}`}
className="absolute right-3 top-3 rounded-full px-2.5 py-1 text-[11px] font-semibold shadow-[0_4px_10px_-4px_rgba(15,29,23,0.25)]"
style={{ background: meta.bg, color: meta.fg }}
>
{store.dealStatus === 'OPEN'
? '거래 가능'
: store.dealStatus === 'MATCHING'
? '매칭 중'
: store.dealStatus}
{meta.label}
</span>
</div>
{/* 정보 영역 */}
<div className="p-4">
<p className="text-xs text-ink-muted">
{store.regionCluster?.nameKo ?? '-'}
@@ -150,10 +159,10 @@ export default async function StoresPage({
<h3 className="mt-1 line-clamp-1 font-display font-bold text-ink">
{store.listingTitle}
</h3>
<div className="mt-3 space-y-1">
<div className="mt-3 space-y-1.5">
<div className="flex items-baseline justify-between">
<span className="text-xs text-ink-muted"></span>
<span className="font-display font-bold text-warm-700">
<span className="font-display font-bold" style={{ color: '#02A149' }}>
{formatKRWShort(premium != null ? Number(premium) : null)}
</span>
</div>
@@ -163,6 +172,12 @@ export default async function StoresPage({
{formatKRWShort(profit != null ? Number(profit) : null)}
</span>
</div>
<div className="flex items-baseline justify-between">
<span className="text-xs text-ink-muted"></span>
<span className="text-sm text-ink-light">
{formatKRWShort(sales != null ? Number(sales) : null)}
</span>
</div>
</div>
</div>
</Link>
@@ -171,7 +186,7 @@ export default async function StoresPage({
)}
</div>
<div className="mt-8 text-center text-sm text-ink-muted">
<div className="mt-8 text-center text-xs text-ink-muted">
·
</div>
</main>
+20 -6
View File
@@ -15,6 +15,7 @@ const STATUS_OPTIONS = [
{ value: 'OPEN', label: '거래 가능' },
{ value: 'MATCHING', label: '매칭 중' },
{ value: 'CONTRACTED', label: '계약 진행 중' },
{ value: 'CLOSED', label: '거래 완료' },
];
export function StoreFilters() {
@@ -46,15 +47,21 @@ export function StoreFilters() {
[router],
);
const selectCls =
'rounded-xl border px-4 py-3 text-sm text-ink bg-white focus:outline-none focus:border-[#03C75A] focus:ring-2 focus:ring-[#03C75A]/20';
const borderStyle = { borderColor: '#E4E8E6' } as const;
return (
<form
onSubmit={handleSubmit}
className="mt-6 flex flex-wrap gap-3 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5"
className="mt-6 flex flex-wrap gap-3 rounded-2xl border bg-white p-5 shadow-[0_2px_14px_-10px_rgba(15,29,23,0.15)]"
style={borderStyle}
>
<select
name="region"
defaultValue={searchParams.get('region') ?? ''}
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
className={selectCls}
style={borderStyle}
>
{REGION_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
@@ -66,7 +73,8 @@ export function StoreFilters() {
name="industryMajor"
value={majorCode}
onChange={(e) => setMajorCode(e.target.value)}
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
className={selectCls}
style={borderStyle}
>
<option value=""> </option>
{INDUSTRY_MAJORS.map((m) => (
@@ -79,7 +87,8 @@ export function StoreFilters() {
name="industry"
defaultValue={searchParams.get('industry') ?? ''}
disabled={!majorCode}
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none disabled:opacity-50"
className={`${selectCls} disabled:opacity-50`}
style={borderStyle}
>
<option value=""> </option>
{subOptions.map((c) => (
@@ -91,7 +100,8 @@ export function StoreFilters() {
<select
name="status"
defaultValue={searchParams.get('status') ?? ''}
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
className={selectCls}
style={borderStyle}
>
{STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
@@ -101,7 +111,11 @@ export function StoreFilters() {
</select>
<button
type="submit"
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
className="rounded-full px-6 py-3 text-sm font-semibold text-white transition-transform hover:-translate-y-0.5"
style={{
background: 'linear-gradient(135deg,#03C75A 0%,#02A149 60%,#018f40 100%)',
boxShadow: '0 10px 24px -10px rgba(3,199,90,0.55)',
}}
>
</button>