feat: 사이트 3구역 개편 + AI 상품 결제 연결 + SEO 업데이트

- 사이드바: AI상품/무료도구/외주의뢰 3그룹 구조로 개편 (ARIA 시맨틱)
- 홈페이지: AI 상품 중심 재작성 (히어로+상품카드+무료도구+외주축소)
- SEO: 메타데이터·OG태그·JSON-LD를 AI 상품 포지셔닝으로 변경
- 프롬프트 페이지: 프리미엄 상품 5개에 PortOne PaymentButton 연결
- AI 키트 페이지: 월 구독 CTA 2곳에 PaymentButton 연결
- 사주: 유료 전환 복원(4,900원) + PaymentButton 연결
- 코드 품질: 인라인 스타일→globals.css, emoji→SVG, 미사용 데이터 제거
- DB 마이그레이션 005: 전체 18개 상품 등록 SQL 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 01:29:42 +09:00
parent 769544b453
commit 9433a3664c
12 changed files with 590 additions and 677 deletions

View File

@@ -38,7 +38,7 @@ export default function PaymentButton({ productId, className, style, children, r
await supabase.from('profiles').upsert({ id: user.id, email: user.email }, { onConflict: 'id' }); await supabase.from('profiles').upsert({ id: user.id, email: user.email }, { onConflict: 'id' });
// 3. Supabase에 order 생성 // 3. Supabase에 order 생성
const paymentId = `order_${Date.now()}_${crypto.randomUUID().slice(0, 8)}`; const paymentId = crypto.randomUUID();
const { error: orderError } = await supabase const { error: orderError } = await supabase
.from('orders') .from('orders')
.insert({ .insert({
@@ -53,10 +53,8 @@ export default function PaymentButton({ productId, className, style, children, r
if (orderError) throw new Error('주문 생성 실패: ' + orderError.message); if (orderError) throw new Error('주문 생성 실패: ' + orderError.message);
// 4. 포트원 V2 결제 요청 // 4. 포트원 V2 결제 요청
const storeId = process.env.NEXT_PUBLIC_PORTONE_STORE_ID!;
const response = await PortOne.requestPayment({ const response = await PortOne.requestPayment({
storeId, storeId: process.env.NEXT_PUBLIC_PORTONE_STORE_ID ?? '',
channelKey: channel.channelKey, channelKey: channel.channelKey,
paymentId, paymentId,
orderName: product.name, orderName: product.name,
@@ -122,7 +120,7 @@ export default function PaymentButton({ productId, className, style, children, r
if (!product) return null; if (!product) return null;
const isTestMode = process.env.NEXT_PUBLIC_PORTONE_STORE_ID?.includes('test') const isTestMode = !process.env.NEXT_PUBLIC_PORTONE_STORE_ID
|| process.env.NODE_ENV === 'development'; || process.env.NODE_ENV === 'development';
return ( return (

View File

@@ -5,76 +5,134 @@ import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client'; import { createClient } from '@/lib/supabase/client';
const navItems = [ /* ── 3구역 네비게이션 구조 ─────────────────────────────────── */
interface NavItem {
href: string;
label: string;
badge?: string;
icon: React.ReactNode;
}
interface NavGroup {
title: string;
items: NavItem[];
}
const navGroups: NavGroup[] = [
{ {
href: '/', title: 'AI 상품',
label: '홈', items: [
icon: ( {
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> href: '/services/prompt',
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> label: '프롬프트 스토어',
</svg> badge: 'HOT',
), icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
},
{
href: '/services/automation',
label: '업무 자동화',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
),
},
{
href: '/services/ai-kit',
label: 'AI 자동화 키트',
badge: '구독',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
),
},
],
}, },
{ {
href: '/services/website', title: '무료 도구',
label: '홈페이지 제작', items: [
badge: 'NEW', {
icon: ( href: '/saju',
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> label: 'AI 사주 분석',
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> badge: '무료',
</svg> icon: (
), <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
),
},
{
href: '/services/lotto',
label: '로또 번호 추천',
badge: '무료',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
},
{
href: '/tools',
label: '도구 쇼케이스',
badge: 'DEMO',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
</svg>
),
},
],
}, },
{ {
href: '/services/automation', title: '외주 의뢰',
label: '업무 자동화', items: [
icon: ( {
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> href: '/freelance',
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> label: '외주 개발 문의',
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> icon: (
</svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
), <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
}, </svg>
{ ),
href: '/services/prompt', },
label: '프롬프트 엔지니어링', {
icon: ( href: '/services/website',
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> label: '홈페이지 제작',
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /> icon: (
</svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
), <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
}, </svg>
{ ),
href: '/services/ai-kit', },
label: 'AI 자동화 키트', ],
badge: 'NEW',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
),
},
{
href: '/saju',
label: 'AI 사주 분석',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
),
},
{
href: '/tools',
label: '여긴 뭐 만들어요?',
badge: 'DEMO',
icon: (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
</svg>
),
}, },
]; ];
/* ── 배지 색상 ─────────────────────────────────────────────── */
function badgeStyle(badge: string) {
switch (badge) {
case 'HOT':
return 'bg-rose-500/15 text-rose-400';
case '구독':
return 'bg-violet-500/15 text-violet-400';
case '무료':
return 'bg-sky-500/15 text-sky-400';
case 'DEMO':
return 'bg-amber-500/15 text-amber-400';
default:
return 'bg-emerald-500/15 text-emerald-400';
}
}
/* ── 컴포넌트 ──────────────────────────────────────────────── */
interface SidebarProps { interface SidebarProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
@@ -129,7 +187,7 @@ export default function Sidebar({ isOpen, onClose }: SidebarProps) {
<div className="px-5 pt-6 pb-5 flex-shrink-0"> <div className="px-5 pt-6 pb-5 flex-shrink-0">
<Link href="/" onClick={onClose} className="block group"> <Link href="/" onClick={onClose} className="block group">
<div className="text-white font-bold text-lg tracking-tight leading-none"></div> <div className="text-white font-bold text-lg tracking-tight leading-none"></div>
<p className="text-slate-500 text-xs mt-1 tracking-tight"> </p> <p className="text-slate-500 text-xs mt-1 tracking-tight">AI · </p>
</Link> </Link>
</div> </div>
@@ -137,34 +195,68 @@ export default function Sidebar({ isOpen, onClose }: SidebarProps) {
<div className="mx-5 h-px bg-white/5 flex-shrink-0" /> <div className="mx-5 h-px bg-white/5 flex-shrink-0" />
{/* Navigation */} {/* Navigation */}
<nav className="flex-1 px-3 py-4 space-y-0.5 overflow-y-auto"> <nav className="flex-1 px-3 py-3 overflow-y-auto" aria-label="메인 메뉴">
{navItems.map((item) => { {/* 홈 */}
const isActive = pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href)); <Link
href="/"
onClick={onClose}
className={`
flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-150 group relative mb-3
${pathname === '/'
? 'bg-white/8 text-white border-l-2 border-blue-500 pl-[10px]'
: 'text-slate-500 hover:text-slate-200 hover:bg-white/5 border-l-2 border-transparent pl-[10px]'
}
`}
>
<span className={`flex-shrink-0 transition-colors ${pathname === '/' ? 'text-blue-400' : 'text-slate-600 group-hover:text-slate-400'}`}>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</span>
<span className={`text-sm font-medium ${pathname === '/' ? 'text-white' : ''}`}></span>
</Link>
{/* Grouped items */}
{navGroups.map((group, gi) => {
const groupId = `nav-group-${gi}`;
return ( return (
<Link <div key={group.title} className={gi > 0 ? 'mt-5' : ''} role="group" aria-labelledby={groupId}>
key={item.href} <h3 id={groupId} className="px-3 mb-2 text-[10px] font-semibold uppercase tracking-[0.15em] text-slate-500">
href={item.href} {group.title}
onClick={onClose} </h3>
className={` <div className="space-y-0.5">
flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-150 group relative {group.items.map((item) => {
${isActive const isActive = pathname === item.href
? 'bg-white/8 text-white border-l-2 border-blue-500 pl-[10px]' || (item.href !== '/' && pathname.startsWith(item.href + '/'));
: 'text-slate-500 hover:text-slate-200 hover:bg-white/5 border-l-2 border-transparent pl-[10px]' return (
} <Link
`} key={item.href}
> href={item.href}
<span className={`flex-shrink-0 transition-colors ${isActive ? 'text-blue-400' : 'text-slate-600 group-hover:text-slate-400'}`}> onClick={onClose}
{item.icon} className={`
</span> flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-150 group relative
<span className={`text-sm font-medium flex-1 truncate ${isActive ? 'text-white' : ''}`}> ${isActive
{item.label} ? 'bg-white/8 text-white border-l-2 border-blue-500 pl-[10px]'
</span> : 'text-slate-500 hover:text-slate-200 hover:bg-white/5 border-l-2 border-transparent pl-[10px]'
{item.badge && ( }
<span className="text-[10px] font-bold px-1.5 py-0.5 rounded bg-emerald-500/15 text-emerald-400 flex-shrink-0"> `}
{item.badge} >
</span> <span className={`flex-shrink-0 transition-colors ${isActive ? 'text-blue-400' : 'text-slate-600 group-hover:text-slate-400'}`}>
)} {item.icon}
</Link> </span>
<span className={`text-sm font-medium flex-1 truncate ${isActive ? 'text-white' : ''}`}>
{item.label}
</span>
{item.badge && (
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded flex-shrink-0 ${badgeStyle(item.badge)}`}>
{item.badge}
</span>
)}
</Link>
);
})}
</div>
</div>
); );
})} })}
</nav> </nav>

View File

@@ -94,6 +94,23 @@ body {
box-shadow: 0 20px 40px rgba(37, 99, 235, 0.12); box-shadow: 0 20px 40px rgba(37, 99, 235, 0.12);
} }
/* Scroll reveal animations */
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
.reveal-d5 { transition-delay: 400ms; }
/* Scrollbar styling */ /* Scrollbar styling */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;

View File

@@ -5,21 +5,22 @@ import DashboardShell from "./components/DashboardShell";
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
default: "쟁승메이드 | 연락 두절 없는 현직 대기업 개발자", default: "쟁승메이드 | AI 프롬프트 · 업무 자동화 · 사주 분석",
template: "%s | 쟁승메이드", template: "%s | 쟁승메이드",
}, },
description: description:
"계약서 먼저, 납기 지키고, 소스코드 100% 인도. 현직 대기업 백엔드 개발자가 운영하는 외주 개발·업무 자동화·RPA 서비스.", "AI 프롬프트 패키지, 업무 자동화 개발, AI 사주 분석까지. 7년차 현직 개발자가 직접 만들고 운영하는 AI 도구 스토어. 9,900원부터.",
keywords: [ keywords: [
"외주 개발", "AI 프롬프트",
"프리랜서 개발자", "ChatGPT 프롬프트",
"업무 자동화", "업무 자동화",
"RPA", "AI 사주",
"웹사이트 제작", "로또 번호 추천",
"AI 자동화 키트", "AI 자동화 키트",
"텔레그램 봇",
"엑셀 자동화",
"프롬프트 엔지니어링", "프롬프트 엔지니어링",
"엑셀 자동화",
"외주 개발",
"홈페이지 제작",
], ],
authors: [{ name: "박재오", url: "https://jaengseung-made.com" }], authors: [{ name: "박재오", url: "https://jaengseung-made.com" }],
creator: "박재오", creator: "박재오",
@@ -28,22 +29,22 @@ export const metadata: Metadata = {
locale: "ko_KR", locale: "ko_KR",
url: "https://jaengseung-made.com", url: "https://jaengseung-made.com",
siteName: "쟁승메이드", siteName: "쟁승메이드",
title: "쟁승메이드 | 연락 두절 없는 현직 대기업 개발자", title: "쟁승메이드 | AI 프롬프트 · 업무 자동화 · 사주 분석",
description: description:
"계약서 먼저, 납기 지키고, 소스코드 100% 인도. 외주 개발·업무 자동화·RPA 전문.", "AI 프롬프트 패키지, 업무 자동화, AI 사주 분석. 7년차 현직 개발자가 만든 AI 도구 스토어.",
images: [ images: [
{ {
url: "https://jaengseung-made.com/og-image.png", url: "https://jaengseung-made.com/og-image.png",
width: 1200, width: 1200,
height: 630, height: 630,
alt: "쟁승메이드 — 연락 두절 없는 개발자", alt: "쟁승메이드 — AI 프롬프트 · 자동화 스토어",
}, },
], ],
}, },
twitter: { twitter: {
card: "summary_large_image", card: "summary_large_image",
title: "쟁승메이드 | 연락 두절 없는 현직 대기업 개발자", title: "쟁승메이드 | AI 프롬프트 · 업무 자동화 · 사주 분석",
description: "계약서 먼저, 납기 지키고, 소스코드 100% 인도.", description: "AI 프롬프트 9,900원~, 업무 자동화, 무료 AI 사주 분석.",
}, },
robots: { robots: {
index: true, index: true,
@@ -64,27 +65,29 @@ const jsonLd = {
worksFor: { '@type': 'Organization', name: '대기업 재직 중' }, worksFor: { '@type': 'Organization', name: '대기업 재직 중' },
email: 'bgg8988@gmail.com', email: 'bgg8988@gmail.com',
telephone: '010-3907-1392', telephone: '010-3907-1392',
knowsAbout: ['Python', 'Java', 'Spring Boot', 'Next.js', 'RPA', 'AI 자동화', '업무 자동화'], knowsAbout: ['Python', 'Java', 'Spring Boot', 'Next.js', 'AI 프롬프트', 'AI 자동화', '업무 자동화', 'ChatGPT', 'Claude'],
description: '현직 대기업 백엔드 개발자. 계약서 먼저, 납기 보장, 소스코드 100% 인도 원칙으로 외주 개발·AI 자동화·프롬프트 엔지니어링 서비스를 제공합니다.', description: '7년차 현직 대기업 백엔드 개발자. AI 프롬프트 패키지, 업무 자동화, AI 사주 분석 등 AI 도구를 직접 개발·운영합니다.',
}, },
{ {
'@type': 'LocalBusiness', '@type': 'LocalBusiness',
'@id': 'https://jaengseung-made.com/#business', '@id': 'https://jaengseung-made.com/#business',
name: '쟁승메이드', name: '쟁승메이드',
url: 'https://jaengseung-made.com', url: 'https://jaengseung-made.com',
description: '현직 대기업 개발자가 운영하는 AI 자동화·외주 개발 서비스. 계약서 포함, 납기 패널티, 소스코드 인도.', description: 'AI 프롬프트 패키지, 업무 자동화, AI 사주 분석. 7년차 현직 개발자가 직접 만들고 운영하는 AI 도구 스토어.',
email: 'bgg8988@gmail.com', email: 'bgg8988@gmail.com',
telephone: '010-3907-1392', telephone: '010-3907-1392',
priceRange: '₩', priceRange: '₩',
areaServed: '대한민국', areaServed: '대한민국',
hasOfferCatalog: { hasOfferCatalog: {
'@type': 'OfferCatalog', '@type': 'OfferCatalog',
name: '쟁승메이드 서비스 목록', name: '쟁승메이드 AI 도구 · 서비스',
itemListElement: [ itemListElement: [
{ '@type': 'Offer', itemOffered: { '@type': 'Product', name: 'AI 프롬프트 패키지', url: 'https://jaengseung-made.com/services/prompt', description: 'ChatGPT·Claude 업무 최적화 프롬프트. 자소서, 마케팅, 이메일, 보고서 등.' } },
{ '@type': 'Offer', itemOffered: { '@type': 'Service', name: '업무 자동화 개발', url: 'https://jaengseung-made.com/services/automation' } }, { '@type': 'Offer', itemOffered: { '@type': 'Service', name: '업무 자동화 개발', url: 'https://jaengseung-made.com/services/automation' } },
{ '@type': 'Offer', itemOffered: { '@type': 'Service', name: '외주 개발', url: 'https://jaengseung-made.com/freelance' } }, { '@type': 'Offer', itemOffered: { '@type': 'Product', name: 'AI 자동화 키트', url: 'https://jaengseung-made.com/services/ai-kit', description: '업무일지·이메일·SNS 자동화 도구 6종 월 구독.' } },
{ '@type': 'Offer', itemOffered: { '@type': 'Service', name: '프롬프트 엔지니어링', url: 'https://jaengseung-made.com/services/prompt' } }, { '@type': 'Offer', itemOffered: { '@type': 'Service', name: 'AI 사주 분석', url: 'https://jaengseung-made.com/saju', description: '생년월일 기반 AI 사주팔자 분석. 무료 체험 가능.' } },
{ '@type': 'Offer', itemOffered: { '@type': 'Service', name: '홈페이지 제작', url: 'https://jaengseung-made.com/services/website' } }, { '@type': 'Offer', itemOffered: { '@type': 'Service', name: '로또 번호 추천', url: 'https://jaengseung-made.com/services/lotto' } },
{ '@type': 'Offer', itemOffered: { '@type': 'Service', name: '맞춤 외주 개발', url: 'https://jaengseung-made.com/freelance' } },
], ],
}, },
}, },

View File

@@ -6,168 +6,87 @@ import ContactModal from './components/ContactModal';
import { trackCTAClick } from '../lib/gtag'; import { trackCTAClick } from '../lib/gtag';
/* ═══════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════
쟁승메이드 홈페이지 — 리뉴얼 v2 쟁승메이드 홈페이지 — v3 (수익 구조 개편)
설계 원칙: 설계 원칙:
1. AI 템플릿 패턴 전면 제거 (orbs, bento, marquee) 1. AI 상품 → 무료 도구 → 외주 순서로 노출
2. 박재오라는 사람이 직접 쓴 느낌 2. 매출 전환 동선 최우선
3. 증거 우선 — running services, 실제 계약 조건 3. 증거 기반 신뢰 확보
4. 에디토리얼 레이아웃 — 타이포 중심
═══════════════════════════════════════════════════ */ ═══════════════════════════════════════════════════ */
const PAIN_POINTS = [ /* ── AI 상품 (매출 핵심) ─────────────────────────── */
const AI_PRODUCTS = [
{ {
icon: ( href: '/services/prompt',
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}> tag: 'BEST',
<path strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" /> tagColor: 'bg-rose-500/15 text-rose-400 border-rose-500/20',
</svg> title: '프롬프트 패키지',
), desc: 'ChatGPT·Claude 업무 최적화 프롬프트. 자소서, 마케팅, 이메일, 보고서 등 즉시 다운로드.',
problem: '중간에 연락이 끊겼다', price: '9,900원~',
desc: '착수금 받고 잠수, 완성 직전에 추가비용 요구. 개발자를 다시 찾아야 하는 상황.', action: '스토어 보기',
color: 'text-red-400 bg-red-400/10',
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
problem: '납기를 3번 넘겼다',
desc: '"다음 주까지 드릴게요"가 두 달 됐다. 런칭 일정이 전부 밀렸다.',
color: 'text-amber-400 bg-amber-400/10',
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
</svg>
),
problem: '소스코드를 못 받았다',
desc: '납품 후 수정이 필요한데 코드를 주지 않는다. 다음 개발자도 이어받을 수 없는 상황.',
color: 'text-violet-400 bg-violet-400/10',
},
{
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
</svg>
),
problem: '뭘 만들고 있는지 모른다',
desc: '중간 보고가 없어서 방향이 맞는지 모른다. "다 되면 연락 준다"는 말만 반복.',
color: 'text-cyan-400 bg-cyan-400/10',
},
];
const PROMISES = [
{
number: '01',
title: '계약서부터 씁니다',
detail: '착수 전에 범위·금액·납기·수정 횟수를 문서로 확정합니다. 구두 약속은 없습니다.',
enforce: '납기 지연 시 하루 총금액 1% 자동 차감 — 계약서 조항',
color: 'border-blue-500/40',
accent: 'text-blue-400',
},
{
number: '02',
title: '주 1회 진행 보고를 드립니다',
detail: '매주 개발 현황·화면·이슈를 정리해서 공유합니다. 중간에 사라지는 일은 구조적으로 없습니다.',
enforce: '보고 누락 시 다음 단계 착수 전 보고 완료 후 진행',
color: 'border-emerald-500/40',
accent: 'text-emerald-400',
},
{
number: '03',
title: '소스코드 전체를 이관합니다',
detail: '완성된 코드 전부를 드립니다. 락인 없음. 이후 다른 개발자에게 인계, 직접 수정도 가능합니다.',
enforce: '납품 완료 후 30일간 코드 관련 질문 무상 답변',
color: 'border-violet-500/40',
accent: 'text-violet-400',
},
{
number: '04',
title: '진행 현황을 마이페이지에서 직접 확인하세요',
detail: '의뢰 접수부터 납품까지 7단계 진행 상황이 고객 마이페이지에 실시간 업데이트됩니다. 오늘 뭐가 진행되는지 물어볼 필요가 없습니다.',
enforce: '단계 전환 시 마이페이지 즉시 반영 보장',
color: 'border-cyan-500/40',
accent: 'text-cyan-400',
},
];
const LIVE_SERVICES = [
{
name: '쟁승메이드',
label: '이 사이트',
url: '/freelance',
desc: '기획·디자인·개발·배포·관리자까지 혼자 만들고 직접 운영 중. 이 사이트 자체가 포트폴리오.',
tech: ['Next.js 16', 'Supabase', 'Tailwind', 'Vercel'],
status: 'live',
color: '#1a56db',
},
{
name: 'AI 사주 분석',
label: '유료 서비스',
url: '/saju',
desc: '생년월일 입력 → Gemini AI가 사주팔자 12항목 즉시 분석. 실제 결제·구매 발생 중.',
tech: ['Google Gemini', 'Supabase', 'PG 연동'],
status: 'live',
color: '#7c3aed',
},
{
name: 'AI 자동화 키트',
label: '월 구독',
url: '/services/ai-kit',
desc: '업무 일지·이메일·SNS 6종 자동화 도구 구독형 서비스. 매달 새 도구 추가.',
tech: ['월 19,900원', 'API 연동', '자동화'],
status: 'live',
color: '#0891b2',
},
];
const SERVICE_LIST = [
{
href: '/services/website',
category: 'WEB',
title: '홈페이지 / 쇼핑몰 제작',
desc: '랜딩 페이지, 기업 소개 사이트, 쇼핑몰 신규 구축 및 리뉴얼',
from: '20만원~',
duration: '3일~',
hot: true,
}, },
{ {
href: '/services/automation', href: '/services/automation',
category: 'RPA', tag: 'HOT',
tagColor: 'bg-amber-500/15 text-amber-400 border-amber-500/20',
title: '업무 자동화 개발', title: '업무 자동화 개발',
desc: '엑셀 처리, 이메일·보고서 자동화, 데이터 수집 스크립트', desc: '엑셀 처리, 이메일·보고서 자동화, 데이터 수집 스크립트. 반복 업무를 없앱니다.',
from: '5만원~', price: '5만원~',
duration: '1일~', action: '자세히 보기',
hot: true,
},
{
href: '/services/prompt',
category: 'AI',
title: '프롬프트 엔지니어링',
desc: 'ChatGPT·Claude 업무 최적화 프롬프트 패키지 설계',
from: '9,900원~',
duration: '1~3일',
hot: false,
}, },
{ {
href: '/services/ai-kit', href: '/services/ai-kit',
category: 'KIT', tag: '구독',
title: 'AI 자동화 키트 구독', tagColor: 'bg-violet-500/15 text-violet-400 border-violet-500/20',
desc: '완성된 자동화 도구를 월 구독으로. 설치 없이 바로 사용.', title: 'AI 자동화 키트',
from: '19,900원/월', desc: '업무일지·이메일·SNS 자동화 도구 6종. 설치 없이 월 구독으로 바로 사용.',
duration: '즉시', price: '19,900원/월',
hot: false, action: '구독 시작',
}, },
];
/* ── 무료 도구 (트래픽 엔진) ─────────────────────── */
const FREE_TOOLS = [
{ {
href: '/saju', href: '/saju',
category: 'AI', icon: (
<svg className="w-6 h-6 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456z" />
</svg>
),
title: 'AI 사주 분석', title: 'AI 사주 분석',
desc: '생년월일 입력 AI가 성격·직업·관계·운세를 즉시 분석', desc: '생년월일 입력하면 AI가 성격·직업·관계·운세를 즉시 분석',
from: '무료', badge: '무료',
duration: '즉시',
hot: false,
}, },
{
href: '/services/lotto',
icon: (
<svg className="w-6 h-6 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m-3-2.818l.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 11-18 0 9 9 0 0118 0z" />
</svg>
),
title: '로또 번호 추천',
desc: '빅데이터·통계 기반 AI 번호 분석. 매주 업데이트.',
badge: '무료',
},
{
href: '/tools',
icon: (
<svg className="w-6 h-6 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
</svg>
),
title: '도구 쇼케이스',
desc: '네이버 블로그 자동화, eBay 리스팅 등 AI 도구 데모',
badge: 'DEMO',
},
];
/* ── 운영 중 서비스 (신뢰 지표) ──────────────────── */
const LIVE_SERVICES = [
{ name: '쟁승메이드', label: '이 사이트' },
{ name: 'AI 사주 분석', label: '유료 서비스' },
{ name: 'AI 자동화 키트', label: '월 구독' },
{ name: '로또 번호 추천', label: '무료 서비스' },
]; ];
function useScrollReveal() { function useScrollReveal() {
@@ -198,22 +117,6 @@ export default function Home() {
return ( return (
<div className="min-h-full" ref={containerRef}> <div className="min-h-full" ref={containerRef}>
<style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
`}</style>
<ContactModal <ContactModal
isOpen={modalOpen} isOpen={modalOpen}
onClose={() => setModalOpen(false)} onClose={() => setModalOpen(false)}
@@ -222,7 +125,6 @@ export default function Home() {
'개발하고 싶은 서비스를 간략히 설명해주세요', '개발하고 싶은 서비스를 간략히 설명해주세요',
'희망 납품 일정과 예산 범위', '희망 납품 일정과 예산 범위',
'참고할 만한 사이트나 레퍼런스', '참고할 만한 사이트나 레퍼런스',
'현재 운영 중인 시스템이 있다면 함께 알려주세요',
]} ]}
accentColor="text-[#5ba4ff]" accentColor="text-[#5ba4ff]"
headerFrom="#04102b" headerFrom="#04102b"
@@ -230,13 +132,12 @@ export default function Home() {
/> />
{/* ══════════════════════════════════════ {/* ══════════════════════════════════════
HERO — 에디토리얼, 단순, 증거 중심 HERO — AI 상품 중심
══════════════════════════════════════ */} ══════════════════════════════════════ */}
<section <section
className="relative overflow-hidden px-6 py-20 lg:px-14 lg:py-28" className="relative overflow-hidden px-6 py-20 lg:px-14 lg:py-28"
style={{ background: '#04102b' }} style={{ background: '#04102b' }}
> >
{/* 배경: 미세한 대각선 패턴만, orbs 없음 */}
<div <div
className="absolute inset-0 opacity-[0.025]" className="absolute inset-0 opacity-[0.025]"
style={{ style={{
@@ -246,80 +147,73 @@ export default function Home() {
/> />
<div className="relative max-w-5xl"> <div className="relative max-w-5xl">
{/* 수동 작성 느낌의 identity tag */}
<div className="flex items-center gap-3 mb-8"> <div className="flex items-center gap-3 mb-8">
<span className="font-mono text-xs text-[#5ba4ff]/60 tracking-[0.2em] uppercase"> <span className="font-mono text-xs text-[#5ba4ff]/60 tracking-[0.2em] uppercase">
· · 7 · AI ·
</span> </span>
<span className="flex items-center gap-1.5 text-xs text-emerald-400/80"> <span className="flex items-center gap-1.5 text-xs text-emerald-400/80">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
</span> </span>
</div> </div>
{/* 헤드라인 — 개발 외주 + 구독 서비스 모두 포괄 */}
<h1 <h1
className="text-[2.6rem] md:text-[3.5rem] lg:text-[4.5rem] font-extrabold leading-[1.12] tracking-tight text-white mb-6" className="text-[2.6rem] md:text-[3.5rem] lg:text-[4.5rem] font-extrabold leading-[1.12] tracking-tight text-white mb-6"
style={{ wordBreak: 'keep-all' }} style={{ wordBreak: 'keep-all' }}
> >
, AI로
<br /> <br />
<span className="text-[#5ba4ff]">.</span>
<br />
<span className="text-[#5ba4ff]"> .</span>
</h1> </h1>
{/* 서브텍스트 */}
<p <p
className="text-[#8ba5cc] text-lg md:text-xl leading-relaxed mb-3 max-w-2xl" className="text-[#8ba5cc] text-lg md:text-xl leading-relaxed mb-3 max-w-2xl"
style={{ wordBreak: 'keep-all' }} style={{ wordBreak: 'keep-all' }}
> >
7 ··AI . , AI .
<br /> <br />
. 7 .
</p> </p>
<p <p
className="text-white/60 text-base md:text-lg leading-relaxed mb-10 max-w-2xl" className="text-white/50 text-base leading-relaxed mb-10 max-w-2xl"
style={{ wordBreak: 'keep-all' }} style={{ wordBreak: 'keep-all' }}
> >
, , . 9,900 AI .
</p> </p>
{/* CTA */}
<div className="flex flex-wrap gap-3 mb-14"> <div className="flex flex-wrap gap-3 mb-14">
<button <Link
onClick={() => { trackCTAClick('무료 상담 신청', '/'); setModalOpen(true); }} href="/services/prompt"
className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors" className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors"
> >
AI
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
</button> </Link>
<Link <Link
href="/freelance" href="/saju"
className="inline-flex items-center gap-2 border border-white/15 hover:border-white/30 text-white/60 hover:text-white px-8 py-4 rounded-xl font-semibold text-sm transition-all" className="inline-flex items-center gap-2 border border-white/15 hover:border-white/30 text-white/60 hover:text-white px-8 py-4 rounded-xl font-semibold text-sm transition-all"
> >
</Link> </Link>
</div> </div>
{/* Live services — stats 대신 실제 운영 서비스 */} {/* Live services indicator */}
<div className="border-t border-white/8 pt-8"> <div className="border-t border-white/8 pt-8">
<p className="font-mono text-[11px] text-[#5ba4ff]/40 tracking-[0.25em] uppercase mb-4"> <p className="font-mono text-[11px] text-[#5ba4ff]/40 tracking-[0.25em] uppercase mb-4">
</p> </p>
<div className="flex flex-wrap gap-6"> <div className="flex flex-wrap gap-6">
{LIVE_SERVICES.map((s) => ( {LIVE_SERVICES.map((s) => (
<Link <span
key={s.name} key={s.name}
href={s.url} className="flex items-center gap-2.5 text-sm text-[#8ba5cc]"
className="group flex items-center gap-2.5 text-sm text-[#8ba5cc] hover:text-white transition-colors"
> >
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse flex-shrink-0" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse flex-shrink-0" />
<span className="font-semibold">{s.name}</span> <span className="font-semibold">{s.name}</span>
<span className="font-mono text-[11px] text-white/25">{s.label}</span> <span className="font-mono text-[11px] text-white/25">{s.label}</span>
</Link> </span>
))} ))}
</div> </div>
</div> </div>
@@ -327,54 +221,107 @@ export default function Home() {
</section> </section>
{/* ══════════════════════════════════════ {/* ══════════════════════════════════════
SECTION 2 — 이런 상황이신가요? SECTION 2 — AI 상품 (매출 핵심)
(고객 고통 공감, 신선한 접근)
══════════════════════════════════════ */} ══════════════════════════════════════ */}
<section className="bg-white px-6 py-16 lg:px-14 lg:py-20"> <section className="bg-white px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="reveal flex items-end justify-between mb-10 flex-wrap gap-4"> <div className="reveal mb-10">
<div> <p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2"> AI Products
Client Pain Points </p>
</p> <h2
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] leading-tight"
className="text-2xl md:text-3xl font-extrabold text-[#04102b] leading-tight" style={{ wordBreak: 'keep-all' }}
style={{ wordBreak: 'keep-all' }} >
> AI
</h2>
</h2> <p className="text-[#64748b] text-sm mt-2" style={{ wordBreak: 'keep-all' }}>
</div> . .
<p className="text-[#64748b] text-sm max-w-xs" style={{ wordBreak: 'keep-all' }}>
.
</p> </p>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid md:grid-cols-3 gap-5">
{PAIN_POINTS.map((p, i) => ( {AI_PRODUCTS.map((p, i) => (
<div <Link
key={p.problem} key={p.href}
className={`reveal reveal-d${i + 1} border border-[#e2e8f0] rounded-2xl p-6 hover:border-[#cbd5e1] hover:shadow-sm transition-all bg-white`} href={p.href}
className={`reveal reveal-d${i + 1} group relative flex flex-col border border-[#e2e8f0] hover:border-[#1a56db]/30 rounded-2xl p-6 transition-all hover:shadow-lg hover:shadow-blue-500/5 bg-white`}
> >
<div className="flex items-start gap-4"> <div className="flex items-center justify-between mb-4">
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${p.color}`}> <span className={`text-[10px] font-bold px-2 py-0.5 rounded-full border ${p.tagColor}`}>
{p.icon} {p.tag}
</div> </span>
<div> <span className="text-[#0f172a] font-extrabold text-sm">{p.price}</span>
<p className="font-bold text-[#0f172a] text-base mb-1">{p.problem}</p>
<p className="text-[#64748b] text-sm leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{p.desc}
</p>
</div>
</div> </div>
</div> <h3 className="text-lg font-extrabold text-[#0f172a] mb-2 group-hover:text-[#1a56db] transition-colors">
{p.title}
</h3>
<p className="text-[#64748b] text-sm leading-relaxed flex-1 mb-5" style={{ wordBreak: 'keep-all' }}>
{p.desc}
</p>
<div className="flex items-center justify-between">
<span className="text-[#1a56db] text-sm font-bold group-hover:underline underline-offset-4">
{p.action}
</span>
</div>
</Link>
))} ))}
</div> </div>
</div> </div>
</section> </section>
{/* ══════════════════════════════════════ {/* ══════════════════════════════════════
SECTION 3 — 박재오는 누구인가 SECTION 3 — 무료 도구 (트래픽 엔진)
(bento 그리드 제거, 에디토리얼 대체) ══════════════════════════════════════ */}
<section className="bg-[#f8faff] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="reveal mb-10">
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
Free Tools
</p>
<h2
className="text-2xl md:text-3xl font-extrabold text-[#04102b] leading-tight"
style={{ wordBreak: 'keep-all' }}
>
</h2>
<p className="text-[#64748b] text-sm mt-2">
.
</p>
</div>
<div className="grid sm:grid-cols-3 gap-4">
{FREE_TOOLS.map((t, i) => (
<Link
key={t.href}
href={t.href}
className={`reveal reveal-d${i + 1} group flex items-start gap-4 bg-white border border-[#e2e8f0] hover:border-[#1a56db]/30 rounded-2xl p-5 transition-all hover:shadow-md`}
>
<span className="flex-shrink-0 mt-0.5">{t.icon}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-bold text-[#0f172a] text-sm group-hover:text-[#1a56db] transition-colors">
{t.title}
</h3>
<span className="text-[10px] font-bold text-sky-500 bg-sky-500/10 px-1.5 py-0.5 rounded">
{t.badge}
</span>
</div>
<p className="text-[#64748b] text-xs leading-relaxed" style={{ wordBreak: 'keep-all' }}>
{t.desc}
</p>
</div>
<svg className="w-4 h-4 text-[#cbd5e1] group-hover:text-[#1a56db] flex-shrink-0 mt-1 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</Link>
))}
</div>
</div>
</section>
{/* ══════════════════════════════════════
SECTION 4 — 누가 만드나요 (About)
══════════════════════════════════════ */} ══════════════════════════════════════ */}
<section className="bg-[#04102b] px-6 py-16 lg:px-14 lg:py-20"> <section className="bg-[#04102b] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
@@ -382,7 +329,6 @@ export default function Home() {
About About
</p> </p>
<div className="reveal grid lg:grid-cols-2 gap-10 lg:gap-16 items-start"> <div className="reveal grid lg:grid-cols-2 gap-10 lg:gap-16 items-start">
{/* 좌측: 스토리 */}
<div> <div>
<h2 <h2
className="text-2xl md:text-3xl font-extrabold text-white leading-tight mb-6" className="text-2xl md:text-3xl font-extrabold text-white leading-tight mb-6"
@@ -390,27 +336,23 @@ export default function Home() {
> >
7 . 7 .
<br /> <br />
<span className="text-[#5ba4ff]"> .</span> <span className="text-[#5ba4ff]"> .</span>
</h2> </h2>
<div className="space-y-4 text-[#8ba5cc] text-base leading-relaxed" style={{ wordBreak: 'keep-all' }}> <div className="space-y-4 text-[#8ba5cc] text-base leading-relaxed" style={{ wordBreak: 'keep-all' }}>
<p> <p>
IT팀에서 7 API , DB , . IT팀에서 7 API , DB , .
</p> </p>
<p> <p>
<span className="text-white">, , AI </span> . <span className="text-white">AI , , AI </span> .
</p>
<p className="text-white/50 text-sm">
267-53-00822 · · bgg8988@gmail.com
</p> </p>
</div> </div>
{/* 기술 스택 — 마퀴 아닌 정적 태그 */}
<div className="mt-8"> <div className="mt-8">
<p className="font-mono text-[11px] text-[#5ba4ff]/40 tracking-widest uppercase mb-3"> <p className="font-mono text-[11px] text-[#5ba4ff]/40 tracking-widest uppercase mb-3">
Tech Stack Tech Stack
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{['Next.js', 'TypeScript', 'Python', 'FastAPI', 'PostgreSQL', 'Supabase', 'Docker', 'AWS', 'Spring Boot', 'Redis'].map((t) => ( {['Next.js', 'TypeScript', 'Python', 'FastAPI', 'PostgreSQL', 'Supabase', 'Docker', 'AWS'].map((t) => (
<span <span
key={t} key={t}
className="font-mono text-xs text-[#5ba4ff]/70 border border-[#5ba4ff]/15 px-2.5 py-1 rounded" className="font-mono text-xs text-[#5ba4ff]/70 border border-[#5ba4ff]/15 px-2.5 py-1 rounded"
@@ -422,38 +364,14 @@ export default function Home() {
</div> </div>
</div> </div>
{/* 우측: 숫자로 증명 */}
<div className="space-y-4"> <div className="space-y-4">
{[ {[
{ { value: '7년', label: '대기업 백엔드 개발 경력', sub: '실제 운영 서비스 다수 개발', color: 'border-blue-500/30' },
value: '7년', { value: '4개', label: '현재 직접 운영 중인 서비스', sub: '이 사이트 포함 — 지금 이 순간도 작동 중', color: 'border-emerald-500/30' },
label: '대기업 백엔드 개발 경력', { value: '100%', label: '외주 시 소스코드 이관', sub: '납품 후 전체 코드 전달, 락인 없음', color: 'border-violet-500/30' },
sub: '실제 운영 서비스 다수 개발', { value: '24h', label: '이내 견적 답변', sub: '주말·공휴일 포함', color: 'border-amber-500/30' },
color: 'border-blue-500/30',
},
{
value: '3개',
label: '현재 직접 운영 중인 서비스',
sub: '이 사이트 포함 — 지금 이 순간도 작동 중',
color: 'border-emerald-500/30',
},
{
value: '100%',
label: '소스코드 이관',
sub: '납품 완료 후 전체 코드 전달, 락인 없음',
color: 'border-violet-500/30',
},
{
value: '24h',
label: '이내 견적 답변',
sub: '주말·공휴일 포함, 평균 3.8시간',
color: 'border-amber-500/30',
},
].map((item) => ( ].map((item) => (
<div <div key={item.value} className={`border-l-2 ${item.color} pl-5 py-2`}>
key={item.value}
className={`border-l-2 ${item.color} pl-5 py-2`}
>
<div className="flex items-baseline gap-3"> <div className="flex items-baseline gap-3">
<span className="text-3xl font-extrabold text-white tracking-tight">{item.value}</span> <span className="text-3xl font-extrabold text-white tracking-tight">{item.value}</span>
<span className="text-[#8ba5cc] text-sm font-medium">{item.label}</span> <span className="text-[#8ba5cc] text-sm font-medium">{item.label}</span>
@@ -467,250 +385,93 @@ export default function Home() {
</section> </section>
{/* ══════════════════════════════════════ {/* ══════════════════════════════════════
SECTION 4약속 3가지 SECTION 5외주 개발 (축소)
(bento 제거, 에디토리얼 행 구조)
══════════════════════════════════════ */}
<section className="bg-[#f8faff] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="reveal mb-10">
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
Guarantee
</p>
<h2
className="text-2xl md:text-3xl font-extrabold text-[#04102b]"
style={{ wordBreak: 'keep-all' }}
>
</h2>
</div>
<div className="reveal space-y-px">
{PROMISES.map((p, i) => (
<div
key={p.number}
className={`flex flex-col md:flex-row md:items-start gap-6 py-8 ${
i < PROMISES.length - 1 ? 'border-b border-[#e2e8f0]' : ''
}`}
>
{/* 번호 */}
<div className={`font-mono text-5xl font-extrabold ${p.accent} opacity-20 leading-none flex-shrink-0 w-16`}>
{p.number}
</div>
{/* 내용 */}
<div className="flex-1">
<h3 className="text-xl font-extrabold text-[#0f172a] mb-2">{p.title}</h3>
<p className="text-[#475569] text-base leading-relaxed mb-3" style={{ wordBreak: 'keep-all' }}>
{p.detail}
</p>
<div className={`inline-flex items-center gap-2 text-xs font-semibold ${p.accent} border ${p.color} px-3 py-1.5 rounded-full`}>
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
{p.enforce}
</div>
</div>
</div>
))}
</div>
</div>
</section>
{/* ══════════════════════════════════════
SECTION 5 — 지금 운영 중인 서비스
(가짜 후기 대신 실제 증거)
══════════════════════════════════════ */}
<section className="bg-[#04102b] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="reveal mb-10">
<p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-2">
Live Portfolio
</p>
<h2
className="text-2xl md:text-3xl font-extrabold text-white"
style={{ wordBreak: 'keep-all' }}
>
</h2>
<p className="text-[#8ba5cc] text-sm mt-2" style={{ wordBreak: 'keep-all' }}>
. .
</p>
</div>
<div className="grid md:grid-cols-3 gap-4">
{LIVE_SERVICES.map((s, i) => (
<Link
key={s.name}
href={s.url}
className={`reveal reveal-d${i + 1} group relative flex flex-col border border-white/8 hover:border-white/20 rounded-2xl p-6 transition-all hover:bg-white/3`}
>
{/* 상단 */}
<div className="flex items-center justify-between mb-4">
<span className="flex items-center gap-1.5 text-xs text-emerald-400 font-semibold">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
</span>
<span
className="text-xs font-mono px-2 py-0.5 rounded border"
style={{ color: s.color, borderColor: `${s.color}40` }}
>
{s.label}
</span>
</div>
<h3 className="text-white font-extrabold text-lg mb-2 group-hover:text-[#5ba4ff] transition-colors">
{s.name}
</h3>
<p className="text-[#8ba5cc] text-sm leading-relaxed mb-4 flex-1" style={{ wordBreak: 'keep-all' }}>
{s.desc}
</p>
{/* 기술 태그 */}
<div className="flex flex-wrap gap-1.5">
{s.tech.map((t) => (
<span key={t} className="font-mono text-[11px] text-white/30 border border-white/8 px-2 py-0.5 rounded">
{t}
</span>
))}
</div>
{/* 링크 화살표 */}
<div className="absolute top-6 right-6 text-white/20 group-hover:text-white/50 transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</div>
</Link>
))}
</div>
</div>
</section>
{/* ══════════════════════════════════════
SECTION 6 — 서비스 목록
(카드 그리드 → 에디토리얼 테이블)
══════════════════════════════════════ */} ══════════════════════════════════════ */}
<section className="bg-white px-6 py-16 lg:px-14 lg:py-20"> <section className="bg-white px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="reveal flex items-end justify-between flex-wrap gap-4 mb-8"> <div className="reveal flex items-end justify-between flex-wrap gap-4 mb-8">
<div> <div>
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">Services</p> <p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
Custom Development
</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]" style={{ wordBreak: 'keep-all' }}> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]" style={{ wordBreak: 'keep-all' }}>
?
</h2> </h2>
<p className="text-[#64748b] text-sm mt-2" style={{ wordBreak: 'keep-all' }}>
, , API .
</p>
</div> </div>
<button
onClick={() => setModalOpen(true)}
className="text-sm text-[#1a56db] font-semibold hover:underline underline-offset-4"
>
</button>
</div> </div>
<div className="reveal divide-y divide-[#f1f5f9]"> <div className="reveal grid sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
{SERVICE_LIST.map((s) => ( {[
<Link { icon: <svg className="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}><path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>, title: '계약서 먼저', desc: '범위·금액·납기를 문서로 확정' },
key={s.href} { icon: <svg className="w-6 h-6 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}><path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" /></svg>, title: '주 1회 보고', desc: '중간에 사라지는 일 없음' },
href={s.href} { icon: <svg className="w-6 h-6 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}><path strokeLinecap="round" strokeLinejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" /></svg>, title: '소스코드 100%', desc: '전체 코드 이관, 락인 없음' },
className="group flex items-center gap-4 md:gap-6 py-5 hover:bg-[#f8faff] -mx-4 px-4 rounded-xl transition-colors" { icon: <svg className="w-6 h-6 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /></svg>, title: '24시간 답변', desc: '주말·공휴일 포함 견적 회신' },
> ].map((item) => (
{/* 카테고리 태그 */} <div key={item.title} className="border border-[#e2e8f0] rounded-xl p-4 text-center">
<span className="font-mono text-xs font-bold text-[#94a3b8] w-10 flex-shrink-0"> <span className="mb-2 block flex justify-center">{item.icon}</span>
{s.category} <p className="font-bold text-[#0f172a] text-sm mb-1">{item.title}</p>
</span> <p className="text-[#94a3b8] text-xs" style={{ wordBreak: 'keep-all' }}>{item.desc}</p>
</div>
{/* 서비스명 + 설명 */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-0.5 flex-wrap">
<span className="font-extrabold text-[#0f172a] text-base group-hover:text-[#1a56db] transition-colors">
{s.title}
</span>
{s.hot && (
<span className="text-[10px] font-bold text-red-500 border border-red-200 px-1.5 py-0.5 rounded bg-red-50">
HOT
</span>
)}
</div>
<p className="text-[#64748b] text-sm truncate" style={{ wordBreak: 'keep-all' }}>
{s.desc}
</p>
</div>
{/* 가격 + 기간 */}
<div className="flex flex-col items-end flex-shrink-0">
<span className="text-[#0f172a] font-extrabold text-xs sm:text-sm">{s.from}</span>
<span className="text-[#94a3b8] text-xs font-mono">{s.duration}</span>
</div>
{/* 화살표 */}
<svg className="w-4 h-4 text-[#cbd5e1] group-hover:text-[#1a56db] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</Link>
))} ))}
</div> </div>
<div className="reveal flex flex-wrap gap-3">
<Link
href="/freelance"
className="inline-flex items-center gap-2 bg-[#04102b] hover:bg-[#0a1a3f] text-white px-6 py-3 rounded-xl font-bold text-sm transition-colors"
>
·
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
<Link
href="/services/website"
className="inline-flex items-center gap-2 border border-[#e2e8f0] hover:border-[#1a56db]/30 text-[#475569] hover:text-[#1a56db] px-6 py-3 rounded-xl font-semibold text-sm transition-all"
>
</Link>
</div>
</div> </div>
</section> </section>
{/* ══════════════════════════════════════ {/* ══════════════════════════════════════
SECTION 7 — 최종 CTA (무료 이벤트 + 상담 통합) SECTION 6 — 최종 CTA
══════════════════════════════════════ */} ══════════════════════════════════════ */}
<section className="bg-[#04102b] px-6 py-20 lg:px-14"> <section className="bg-[#04102b] px-6 py-20 lg:px-14">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
{/* 무료 이벤트 배너 */}
<div className="reveal border border-white/8 rounded-2xl p-6 md:p-8 mb-10 flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div>
<div className="inline-flex items-center gap-2 bg-red-500/15 border border-red-500/20 text-red-400 text-xs font-bold px-3 py-1 rounded-full mb-3">
<span className="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" />
3
</div>
<h3 className="text-white font-extrabold text-xl mb-1" style={{ wordBreak: 'keep-all' }}>
, 3 20%
</h3>
<p className="text-[#8ba5cc] text-sm" style={{ wordBreak: 'keep-all' }}>
· . .
</p>
</div>
<button
onClick={() => setModalOpen(true)}
className="flex-shrink-0 bg-white text-[#04102b] font-extrabold text-sm px-7 py-3.5 rounded-xl hover:bg-[#f1f5ff] transition-colors whitespace-nowrap"
>
</button>
</div>
{/* 메인 CTA */}
<div className="reveal text-center"> <div className="reveal text-center">
<p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-4">Get Started</p> <p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-4">Get Started</p>
<h2 <h2
className="text-3xl md:text-5xl font-extrabold text-white mb-4 leading-tight" className="text-3xl md:text-5xl font-extrabold text-white mb-4 leading-tight"
style={{ wordBreak: 'keep-all' }} style={{ wordBreak: 'keep-all' }}
> >
AI
<br /> <br />
. .
</h2> </h2>
<p className="text-[#8ba5cc] text-lg mb-10" style={{ wordBreak: 'keep-all' }}> <p className="text-[#8ba5cc] text-lg mb-10" style={{ wordBreak: 'keep-all' }}>
. 24 . 9,900 . .
</p> </p>
<div className="flex flex-wrap gap-4 justify-center"> <div className="flex flex-wrap gap-4 justify-center">
<button <Link
onClick={() => setModalOpen(true)} href="/services/prompt"
className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-10 py-4 rounded-xl font-extrabold text-base transition-colors" className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-10 py-4 rounded-xl font-extrabold text-base transition-colors"
> >
AI
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
</button> </Link>
<a <button
href="https://open.kakao.com/o/s9stoNvb" onClick={() => { trackCTAClick('무료 상담 신청', '/'); setModalOpen(true); }}
target="_blank" className="inline-flex items-center gap-2 border border-white/15 hover:border-white/30 text-white/70 hover:text-white px-10 py-4 rounded-xl font-extrabold text-base transition-all"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-10 py-4 rounded-xl font-extrabold text-base transition-all"
style={{ background: '#FEE500', color: '#3A1D1D' }}
> >
</a> </button>
</div> </div>
<p className="text-white/20 text-xs mt-8 font-mono"> <p className="text-white/20 text-xs mt-8 font-mono">
· 267-53-00822 · bgg8988@gmail.com · 010-3907-1392 · 267-53-00822 · bgg8988@gmail.com · 010-3907-1392

56
app/payment/test/page.tsx Normal file
View File

@@ -0,0 +1,56 @@
'use client';
import PaymentButton from '@/app/components/PaymentButton';
import { PRODUCTS } from '@/lib/products';
// DB products 테이블에 등록된 상품만 테스트 가능
const TEST_PRODUCTS = [
'saju_detail', // 4,900원
'lotto_gold', // 900원/월
'lotto_platinum', // 2,900원/월
'lotto_diamond', // 9,900원/월
];
export default function PaymentTestPage() {
return (
<div className="max-w-2xl mx-auto px-6 py-12">
<div className="mb-8">
<h1 className="text-2xl font-extrabold text-[#04102b] mb-2"> </h1>
<p className="text-slate-500 text-sm">
V2 .
</p>
<div className="mt-3 bg-amber-50 border border-amber-200 text-amber-800 text-xs px-4 py-2.5 rounded-xl">
. .
</div>
</div>
<div className="space-y-4">
{TEST_PRODUCTS.map((id) => {
const product = PRODUCTS[id];
if (!product) return null;
return (
<div
key={id}
className="flex items-center justify-between bg-white border border-slate-200 rounded-xl px-5 py-4"
>
<div>
<p className="font-semibold text-sm text-slate-800">{product.name}</p>
<p className="text-xs text-slate-400 mt-0.5">
{product.price.toLocaleString()}
{product.type === 'monthly' && ' / 월'}
<span className="ml-2 text-slate-300">({id})</span>
</p>
</div>
<PaymentButton
productId={id}
className="bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-5 py-2.5 rounded-xl text-sm font-bold transition shadow-lg shadow-blue-600/20"
>
</PaymentButton>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -2,7 +2,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
// PaymentButton 비활성화 — 토스페이먼츠 결제 일시 중단 import PaymentButton from '@/app/components/PaymentButton';
import { createClient } from '@/lib/supabase/client'; import { createClient } from '@/lib/supabase/client';
const faqItems = [ const faqItems = [
@@ -202,7 +202,7 @@ export default function SajuPage() {
<div className="text-center mb-8"> <div className="text-center mb-8">
<p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">PRICING</p> <p className="text-[#1a56db] text-xs font-bold uppercase tracking-widest mb-2">PRICING</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] tracking-tight"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] tracking-tight"> </h2>
<p className="text-slate-500 text-sm mt-2"> AI </p> <p className="text-slate-500 text-sm mt-2"> , AI 4,900</p>
</div> </div>
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
@@ -256,8 +256,8 @@ export default function SajuPage() {
backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)', backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)',
}} }}
> >
<div className="absolute top-4 right-4 bg-emerald-400 text-[#04102b] text-xs font-bold px-2 py-0.5 rounded-lg"> <div className="absolute top-4 right-4 bg-amber-400 text-[#04102b] text-xs font-bold px-2 py-0.5 rounded-lg">
4,900
</div> </div>
<div className="flex items-center gap-3 mb-5 relative"> <div className="flex items-center gap-3 mb-5 relative">
<div className="w-10 h-10 rounded-xl bg-violet-500/20 border border-violet-400/30 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-violet-500/20 border border-violet-400/30 flex items-center justify-center">
@@ -277,7 +277,7 @@ export default function SajuPage() {
'용신·희신·기신 추정', '용신·희신·기신 추정',
'대운 (10년 주기) 분석', '대운 (10년 주기) 분석',
'올해 세운 흐름', '올해 세운 흐름',
'GPT-4o AI 12가지 상세 해석', 'Gemini 2.5 Pro AI 12가지 상세 해석',
].map((item) => ( ].map((item) => (
<li key={item} className="flex items-center gap-2.5 text-sm text-blue-200"> <li key={item} className="flex items-center gap-2.5 text-sm text-blue-200">
<div className="w-4 h-4 rounded-full bg-amber-400/20 border border-amber-400/40 flex items-center justify-center flex-shrink-0"> <div className="w-4 h-4 rounded-full bg-amber-400/20 border border-amber-400/40 flex items-center justify-center flex-shrink-0">
@@ -288,13 +288,16 @@ export default function SajuPage() {
))} ))}
</ul> </ul>
<div className="mt-6 pt-5 border-t border-white/10 relative"> <div className="mt-6 pt-5 border-t border-white/10 relative">
<div className="text-lg font-bold text-emerald-400 mb-1"> </div> <div className="flex items-baseline gap-2 mb-1">
<div className="text-xs text-blue-300/70 mt-1 mb-4"> · 12 AI </div> <span className="text-2xl font-extrabold text-white">4,900</span>
<span className="text-xs text-blue-300/50">/ 1</span>
</div>
<div className="text-xs text-blue-300/70 mt-1 mb-4"> · 12 AI </div>
<Link <Link
href="/saju/input" href="/saju/input"
className="block w-full text-center py-3 rounded-xl text-sm font-bold transition bg-amber-400 text-[#04102b] hover:bg-amber-300" className="block w-full text-center py-3 rounded-xl text-sm font-bold transition bg-amber-400 text-[#04102b] hover:bg-amber-300"
> >
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
// PaymentButton 비활성화 — 토스페이먼츠 결제 일시 중단, 무료 제공 중 import PaymentButton from '@/app/components/PaymentButton';
interface BirthKey { interface BirthKey {
birth_year: number; birth_year: number;
@@ -313,13 +313,13 @@ export default function SajuAISection({
))} ))}
</div> </div>
{/* 결제 일시 중단 — hasPaid=true이므로 이 분기는 표시되지 않음 */} <PaymentButton
<a productId="saju_detail"
href={process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? '/freelance?service=AI사주분석'}
className="inline-flex items-center gap-2 bg-amber-400 hover:bg-amber-300 text-[#04102b] font-bold px-7 py-3 rounded-xl transition-all" className="inline-flex items-center gap-2 bg-amber-400 hover:bg-amber-300 text-[#04102b] font-bold px-7 py-3 rounded-xl transition-all"
> >
AI AI 4,900
</a> </PaymentButton>
<p className="text-blue-200/40 text-xs mt-3"> AI · </p>
</div> </div>
</div> </div>
); );

View File

@@ -76,8 +76,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
const solarTermName = getSolarTermName(solarTermIndex); const solarTermName = getSolarTermName(solarTermIndex);
// ── 결제 여부 + 저장된 AI 해석 + 로또 구독 확인 ───────────────────── // ── 결제 여부 + 저장된 AI 해석 + 로또 구독 확인 ─────────────────────
// 토스페이먼츠 결제 일시 중단 — AI 사주 해석 무료 제공 중 let hasPaid = false;
let hasPaid = true;
let savedInterpretation: string | null = null; let savedInterpretation: string | null = null;
let hasLottoSubscription = false; let hasLottoSubscription = false;
try { try {

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import ContactModal from '../../components/ContactModal'; import ContactModal from '../../components/ContactModal';
import PaymentButton from '../../components/PaymentButton';
const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null; const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null;
@@ -252,30 +253,18 @@ export default function AiKitPage() {
<p className="text-indigo-300/60 text-xs mt-1"> · </p> <p className="text-indigo-300/60 text-xs mt-1"> · </p>
</div> </div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 items-center">
{KAKAO_CHANNEL_URL ? ( <PaymentButton
<a productId="ai_kit_monthly"
href={KAKAO_CHANNEL_URL} className="inline-flex items-center justify-center gap-2 bg-indigo-500 hover:bg-indigo-400 text-white text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-xs"
target="_blank" >
rel="noopener noreferrer" 19,900/
className="inline-flex items-center justify-center gap-2 bg-[#FEE500] hover:bg-[#F5DC00] text-[#3A1D1D] text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-xs" </PaymentButton>
>
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor"><path d="M12 3C6.477 3 2 6.477 2 10.5c0 2.438 1.418 4.6 3.584 5.977l-.916 3.41c-.086.32.283.573.56.38l4.014-2.674A11.29 11.29 0 0012 18c5.523 0 10-3.477 10-7.5S17.523 3 12 3z"/></svg>
</a>
) : (
<button
onClick={() => setModalOpen(true)}
className="bg-blue-600 hover:bg-blue-500 text-white text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-xs"
>
{totalMonthlySaving}
</button>
)}
<button <button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
className="text-indigo-300/60 hover:text-indigo-300 text-sm underline underline-offset-2 transition-colors" className="text-indigo-300/60 hover:text-indigo-300 text-sm underline underline-offset-2 transition-colors"
> >
</button> </button>
</div> </div>
</div> </div>
@@ -566,25 +555,13 @@ export default function AiKitPage() {
<br /> . <br /> .
</p> </p>
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
{KAKAO_CHANNEL_URL ? ( <PaymentButton
<a productId="ai_kit_monthly"
href={KAKAO_CHANNEL_URL} className="inline-flex items-center justify-center gap-2 bg-indigo-500 hover:bg-indigo-400 text-white text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-sm"
target="_blank" >
rel="noopener noreferrer" 19,900/
className="inline-flex items-center justify-center gap-2 bg-[#FEE500] hover:bg-[#F5DC00] text-[#3A1D1D] text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-sm" </PaymentButton>
> <p className="text-white/25 text-xs"> · 19,900 · </p>
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor"><path d="M12 3C6.477 3 2 6.477 2 10.5c0 2.438 1.418 4.6 3.584 5.977l-.916 3.41c-.086.32.283.573.56.38l4.014-2.674A11.29 11.29 0 0012 18c5.523 0 10-3.477 10-7.5S17.523 3 12 3z"/></svg>
19,900/
</a>
) : (
<button
onClick={() => setModalOpen(true)}
className="bg-indigo-600 hover:bg-indigo-500 text-white text-base font-bold px-8 py-4 rounded-xl transition-colors w-full max-w-sm"
>
{totalMonthlySaving}
</button>
)}
<p className="text-white/25 text-xs"> · 19,900 · </p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import ContactModal from '../../components/ContactModal'; import ContactModal from '../../components/ContactModal';
import PaymentButton from '../../components/PaymentButton';
import { trackCTAClick } from '../../../lib/gtag'; import { trackCTAClick } from '../../../lib/gtag';
const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null; const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null;
@@ -381,21 +382,6 @@ export default function PromptPage() {
return ( return (
<div ref={containerRef} className="min-h-full bg-[#f0f5ff]"> <div ref={containerRef} className="min-h-full bg-[#f0f5ff]">
<style>{` <style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
.reveal-d5 { transition-delay: 400ms; }
.prompt-card { .prompt-card {
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1); box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
@@ -522,28 +508,15 @@ export default function PromptPage() {
</div> </div>
{/* CTA */} {/* CTA */}
<div className="p-6"> <div className="p-6">
{KAKAO_CHANNEL_URL ? ( <PaymentButton
<a productId={product.productId}
href={KAKAO_CHANNEL_URL} className="flex items-center justify-center gap-2 w-full py-3.5 rounded-xl text-sm font-extrabold transition-all hover:opacity-90"
target="_blank" style={{ background: product.accentColor, color: product.bgFrom }}
rel="noopener noreferrer" >
className="flex items-center justify-center gap-2 w-full py-3.5 rounded-xl text-sm font-extrabold transition-all hover:opacity-90"
style={{ background: '#FEE500', color: '#3A1D1D' }} </PaymentButton>
>
<svg viewBox="0 0 24 24" className="w-4 h-4" fill="currentColor"><path d="M12 3C6.477 3 2 6.477 2 10.5c0 2.438 1.418 4.6 3.584 5.977l-.916 3.41c-.086.32.283.573.56.38l4.014-2.674A11.29 11.29 0 0012 18c5.523 0 10-3.477 10-7.5S17.523 3 12 3z"/></svg>
</a>
) : (
<button
onClick={() => openModal(`프롬프트 패키지 — ${product.title}`)}
className="w-full py-3.5 rounded-xl text-sm font-extrabold transition-all hover:opacity-90"
style={{ background: product.accentColor, color: product.bgFrom }}
>
</button>
)}
<p className="text-center text-xs mt-2" style={{ color: 'rgba(255,255,255,0.3)' }}> <p className="text-center text-xs mt-2" style={{ color: 'rgba(255,255,255,0.3)' }}>
·
</p> </p>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,34 @@
-- ============================================================
-- Migration 005: lib/products.ts의 모든 상품을 DB에 등록
-- Supabase SQL Editor에서 실행하세요
-- ============================================================
insert into public.products (id, name, description, price, category) values
-- 주식
('stock_starter_install', '주식 스타터 설치', '1개 종목 자동 매매 설치', 99000, 'stock'),
('stock_pro_install', '주식 프로 설치', '5개 종목 + 전략 커스터마이징 설치', 199000, 'stock'),
('stock_starter_monthly', '주식 스타터 월 유지비', '스타터 월 유지보수 비용', 29000, 'stock'),
('stock_pro_monthly', '주식 프로 월 유지비', '프로 월 유지보수 비용', 49000, 'stock'),
-- 프롬프트
('prompt_single', '프롬프트 단건 설계', '업무 특화 프롬프트 1개 맞춤 설계', 30000, 'prompt'),
('prompt_business', '프롬프트 비즈니스 패키지', '업무 유형별 프롬프트 5개 설계', 99000, 'prompt'),
('prompt_team', '프롬프트 팀/기업 패키지', '팀 전체 프롬프트 시스템 구축', 249000, 'prompt'),
('prompt_image_gen', 'AI 이미지 생성 마스터 프롬프트', '50종 이미지 생성 프롬프트', 12900, 'prompt'),
('prompt_resume', 'AI 자소서·이력서 첨삭 프롬프트', '7가지 유형별 자소서 프롬프트', 9900, 'prompt'),
('prompt_email', '비즈니스 이메일 마스터 프롬프트', '20종 비즈니스 이메일 프롬프트', 10900, 'prompt'),
('prompt_marketing', '마케팅 카피·SNS 콘텐츠 프롬프트', '플랫폼별 카피 프롬프트 30종', 12900, 'prompt'),
('prompt_report', '업무 보고서·기획서 작성 프롬프트', '보고서/기획서/회의록 프롬프트 25종', 10900, 'prompt'),
-- 업무 자동화
('automation_basic', '단순 업무 자동화', '단일 반복 업무 자동화 1건 개발', 50000, 'automation'),
('automation_advanced', '업무 자동화 심화', '복합 업무 자동화 개발 · RPA·API 연동', 150000, 'automation'),
-- 홈페이지
('website_starter', '홈페이지 스타터 패키지', '5페이지 이내 반응형 홈페이지', 200000, 'website'),
('website_business', '홈페이지 비즈니스 패키지', '10페이지 이내 · 관리자 페이지 · SEO', 1000000, 'website'),
('website_premium', '홈페이지 프리미엄 패키지', '페이지 수 무제한 · 결제/회원 시스템', 2000000, 'website'),
-- AI 키트
('ai_kit_monthly', 'AI 자동화 월 구독 키트', '소상공인·직장인 AI 자동화 도구 월 구독', 19900, 'ai_kit')
on conflict (id) do update set
name = excluded.name,
description = excluded.description,
price = excluded.price,
category = excluded.category;