feat: 도구 쇼케이스 리디자인 + 서비스 페이지 스크롤 애니메이션 + followup 파이프라인

- /tools 페이지: Supanova 디자인 원칙 적용, 비대칭 레이아웃·지그재그 카드·CTA 리디자인
- /tools SEO: layout.tsx 분리하여 메타데이터·OG 태그 추가
- /services/prompt: 스크롤 리빌 애니메이션 (IntersectionObserver + stagger delay)
- /services/automation: 스크롤 리빌 애니메이션 (전 섹션 적용)
- /followup 커맨드: 지원서 팔로업 → 수주 클로징 파이프라인 신규 생성

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 07:24:30 +09:00
parent 3537862c99
commit c7bf0253e3
6 changed files with 579 additions and 120 deletions

View File

@@ -0,0 +1,135 @@
# /followup — 지원서 팔로업 & 수주 클로징 파이프라인
당신은 **쟁승메이드**의 지원서 팔로업 전문 파이프라인입니다.
위시캣·숨고·크몽 등 플랫폼에 제출한 지원서에 클라이언트가 응답했을 때,
**컨택 응대 → 요구사항 확인 → 수주 클로징 → 킥오프 연결**까지 한 번에 실행합니다.
기존 `/intake`(신규 문의)와 `/kickoff`(계약 확정 후) 사이의 빈 구간을 채우는 파이프라인입니다.
```
[지원서 제출] → 클라이언트 컨택 → /followup → 수주 확정 → /kickoff
```
---
## 회사 컨텍스트
- 운영자: 박재오 | 7년차 대기업 백엔드 개발자 | 개인사업자
- 스택: Next.js 16 / Supabase / Vercel / FastAPI
- 계약 조건: 선금 50% / 잔금 50% / 납기 지연 1일당 1% 차감
- 프로젝트 메모리: `.claude/projects/.../memory/project_proposals.md`에 제출한 지원서 상세 내용이 있음. 반드시 참조할 것.
---
## 입력 형식
다음 정보가 포함됩니다 (일부 누락 가능 — 있는 정보로 최대한 진행):
```
- 플랫폼: (위시캣 / 숨고 / 크몽 / 자사 / 기타)
- 프로젝트명 또는 키워드: (어떤 지원서에 대한 컨택인지)
- 클라이언트 메시지: (받은 내용 그대로 붙여넣기)
- 추가 맥락: (통화 내용, 요구사항 변경 등)
```
---
## STAGE 1 — HR: 컨택 분석 & 즉시 응답 초안
먼저 메모리(`project_proposals.md`)에서 해당 지원서를 찾아 원래 제안 내용을 확인하세요.
그 위에 클라이언트의 컨택 내용을 대조 분석합니다.
```
[컨택 분석]
- 원 지원서 요약: (제출했던 금액·기간·핵심 포지셔닝)
- 클라이언트 반응 톤: (긍정적 / 탐색적 / 가격 흥정 / 추가 요구 / 비교 검토 중)
- 핵심 관심사: (클라이언트가 가장 궁금해하는 것)
- 숨은 니즈: (직접 말하지 않았지만 메시지에서 읽히는 것)
- 경쟁 상황: (다른 개발자도 지원했을 가능성, 비교 포인트)
- 긴급도: (즉시 응답 필요 / 24시간 내 / 여유)
```
**즉시 응답 메시지** (플랫폼 메시지용, 300자 이내):
- 빠른 감사 인사
- 핵심 질문 1~2개 (요구사항 구체화용)
- 미팅/통화 제안
- 전문성 한 줄 어필
이 메시지는 **지금 바로 보낼 수 있는 수준**이어야 합니다.
플랫폼 응답률은 수주 확률에 직접 영향을 미치므로 속도가 중요합니다.
---
## STAGE 2 — PM: 프로젝트 실현 가능성 & 일정 검토
현재 진행 중인 프로젝트와 리소스를 고려하여 판단합니다.
```
[실현 가능성 검토]
- 현재 진행 중 프로젝트: (있다면 병렬 가능 여부)
- 착수 가능 시점: (즉시 / X일 후)
- 원 지원서 대비 변경 사항: (금액·기간·범위 조정 필요 여부)
- 일정 리스크: (타이트한 부분, 의존성)
- Go / No-Go 판단: (수주 추천 / 조건부 추천 / 비추천 + 이유)
```
**조건부일 경우**: 어떤 조건이 충족되면 Go인지 명시
**No-Go일 경우**: 거절 시 관계 유지 전략 포함 (향후 재의뢰 가능성)
---
## STAGE 3 — Developer: 기술 사전 준비 체크
클라이언트의 추가 요구사항이나 변경 사항을 기술 관점에서 빠르게 검토합니다.
```
[기술 사전 검토]
- 원 지원서 기술 검토 유지 여부: (변경 없음 / 수정 필요)
- 추가 요구사항 기술 타당성: (가능 / 사전 검증 필요 / 불가)
- 공수 변동: (원 지원서 대비 ±X일, 이유)
- 사전에 확인해야 할 것: (기존 코드 접근, API 키, 테스트 환경 등)
- 킥오프 시 즉시 착수 가능한 작업: (환경 세팅, 스키마 설계 등)
```
---
## STAGE 4 — HR: 수주 클로징 전략 & CEO 브리핑
STAGE 1~3을 통합하여 최종 의사결정 자료를 만듭니다.
**A. 클로징 전략**
```
[수주 클로징 전략]
- 추천 접근법: (가격 유지 / 할인 제안 / 옵션 분리 / 단계별 제안)
- 협상 시나리오:
· 클라이언트가 가격 인하 요청 시 → (대응 전략 + 마지노선)
· 범위 추가 요청 시 → (분리 견적 or 패키지 업그레이드)
· 일정 단축 요청 시 → (가능 범위 + 추가 비용)
- 차별화 포인트: (경쟁 개발자 대비 우리가 앞서는 것)
- 클로징 멘트: (결정을 유도하는 마무리 문구)
```
**B. CEO 브리핑 (박재오에게)**
```
[CEO 의사결정 요약]
- 프로젝트: [이름]
- 플랫폼: [위시캣/숨고/크몽]
- 제안 금액: X원 → 조정 금액: X원
- 예상 공수: X일
- 수주 추천도: ★★★★☆ (5점 중)
- 핵심 판단: (한 줄 — 왜 받아야/말아야 하는지)
- 다음 액션: (통화 예약 / 견적서 재발송 / 계약서 전달)
- ⚡ 긴급도: (지금 바로 / 오늘 중 / 내일까지)
```
**C. 수주 확정 시 → 킥오프 연결**
```
수주가 확정되면 다음 커맨드를 실행하세요:
/kickoff [프로젝트명] — [고객명] — [계약금액] — [납기]
```
---
## 클라이언트 메시지
$ARGUMENTS

View File

@@ -79,6 +79,7 @@ app/
| 커맨드 | 실행 파이프라인 | 사용 시점 | | 커맨드 | 실행 파이프라인 | 사용 시점 |
|--------|----------------|-----------| |--------|----------------|-----------|
| `/intake [문의내용]` | HR → PM → Developer → HR | 신규 고객 문의 접수 시 | | `/intake [문의내용]` | HR → PM → Developer → HR | 신규 고객 문의 접수 시 |
| `/followup [컨택내용]` | HR → PM → Developer → HR | 지원서에 클라이언트가 컨택 시 |
| `/kickoff [프로젝트정보]` | PM → Developer → Designer → HR | 계약 확정 후 프로젝트 시작 시 | | `/kickoff [프로젝트정보]` | PM → Developer → Designer → HR | 계약 확정 후 프로젝트 시작 시 |
| `/weekly [이번주현황]` | PM → Evaluator → Marketing → PM | 매주 금요일 주간 리뷰 | | `/weekly [이번주현황]` | PM → Evaluator → Marketing → PM | 매주 금요일 주간 리뷰 |
| `/campaign [목적/아이디어]` | Marketing → Marketing(카피) → Designer → Marketing(실행) | 마케팅 캠페인 기획·실행 시 | | `/campaign [목적/아이디어]` | Marketing → Marketing(카피) → Designer → Marketing(실행) | 마케팅 캠페인 기획·실행 시 |

View File

@@ -1,9 +1,31 @@
'use client'; 'use client';
import { useState } 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';
function useScrollReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
);
el.querySelectorAll('.reveal').forEach((child) => observer.observe(child));
return () => observer.disconnect();
}, []);
return ref;
}
const tools = [ const tools = [
{ {
id: 'excel', id: 'excel',
@@ -163,6 +185,7 @@ const process = [
export default function AutomationPage() { export default function AutomationPage() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [modalService, setModalService] = useState('업무 자동화'); const [modalService, setModalService] = useState('업무 자동화');
const containerRef = useScrollReveal();
const openModal = (service: string) => { const openModal = (service: string) => {
setModalService(service); setModalService(service);
@@ -170,7 +193,22 @@ export default function AutomationPage() {
}; };
return ( return (
<div className="min-h-full bg-[#f0f5ff]"> <div ref={containerRef} className="min-h-full bg-[#f0f5ff]">
<style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
`}</style>
<ContactModal <ContactModal
isOpen={modalOpen} isOpen={modalOpen}
onClose={() => setModalOpen(false)} onClose={() => setModalOpen(false)}
@@ -212,13 +250,13 @@ export default function AutomationPage() {
{/* ─── 자동화 유형 ─── */} {/* ─── 자동화 유형 ─── */}
<div className="px-6 py-12 lg:px-12"> <div className="px-6 py-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">AUTOMATION TYPES</p> <p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">AUTOMATION TYPES</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div> </div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{automationTypes.map((at) => ( {automationTypes.map((at, idx) => (
<div key={at.title} className={`bg-white rounded-2xl border-2 ${at.accentColor} p-5`}> <div key={at.title} className={`bg-white rounded-2xl border-2 ${at.accentColor} p-5 reveal reveal-d${(idx % 3) + 1}`}>
<span className={`inline-block text-xs font-bold px-2 py-0.5 rounded-md border mb-3 ${at.labelColor}`}>{at.title.split(' ')[0]}</span> <span className={`inline-block text-xs font-bold px-2 py-0.5 rounded-md border mb-3 ${at.labelColor}`}>{at.title.split(' ')[0]}</span>
<h3 className="font-bold text-[#04102b] text-sm mb-2">{at.title}</h3> <h3 className="font-bold text-[#04102b] text-sm mb-2">{at.title}</h3>
<p className="text-slate-500 text-xs leading-relaxed mb-3">{at.desc}</p> <p className="text-slate-500 text-xs leading-relaxed mb-3">{at.desc}</p>
@@ -239,15 +277,15 @@ export default function AutomationPage() {
{/* ─── 프로세스 ─── */} {/* ─── 프로세스 ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">PROCESS</p> <p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">PROCESS</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div> </div>
<div className="relative"> <div className="relative">
<div className="hidden sm:block absolute top-10 left-[10%] right-[10%] h-0.5 bg-[#dbe8ff]" /> <div className="hidden sm:block absolute top-10 left-[10%] right-[10%] h-0.5 bg-[#dbe8ff]" />
<div className="grid grid-cols-1 sm:grid-cols-5 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-5 gap-4">
{process.map((p) => ( {process.map((p, idx) => (
<div key={p.step} className="relative text-center"> <div key={p.step} className={`relative text-center reveal reveal-d${(idx % 3) + 1}`}>
<div className="w-20 h-20 mx-auto rounded-2xl bg-[#012030] border border-cyan-400/20 flex flex-col items-center justify-center mb-3"> <div className="w-20 h-20 mx-auto rounded-2xl bg-[#012030] border border-cyan-400/20 flex flex-col items-center justify-center mb-3">
<span className="text-cyan-400 text-xs font-bold">STEP</span> <span className="text-cyan-400 text-xs font-bold">STEP</span>
<span className="text-white font-extrabold text-lg leading-none">{p.step}</span> <span className="text-white font-extrabold text-lg leading-none">{p.step}</span>
@@ -264,13 +302,13 @@ export default function AutomationPage() {
{/* ─── 예상 비용 ─── */} {/* ─── 예상 비용 ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<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]"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div> </div>
<div className="grid sm:grid-cols-3 gap-5"> <div className="grid sm:grid-cols-3 gap-5">
{plans.map((plan) => ( {plans.map((plan, idx) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col ${ <div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col reveal reveal-d${idx + 1} ${
plan.highlight plan.highlight
? 'bg-[#012030] border-cyan-400/30 shadow-2xl shadow-cyan-900/20 scale-105' ? 'bg-[#012030] border-cyan-400/30 shadow-2xl shadow-cyan-900/20 scale-105'
: 'bg-white border-[#dbe8ff]' : 'bg-white border-[#dbe8ff]'
@@ -304,7 +342,7 @@ export default function AutomationPage() {
{/* ─── 프리미엄 툴 ─── */} {/* ─── 프리미엄 툴 ─── */}
<div className="px-6 pb-4 lg:px-12"> <div className="px-6 pb-4 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<span className="inline-flex items-center gap-1.5 bg-amber-500/10 border border-amber-400/30 text-amber-700 text-xs font-bold px-3 py-1 rounded-full mb-3"> <span className="inline-flex items-center gap-1.5 bg-amber-500/10 border border-amber-400/30 text-amber-700 text-xs font-bold px-3 py-1 rounded-full mb-3">
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg> <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
PREMIUM TOOLS PREMIUM TOOLS
@@ -313,9 +351,9 @@ export default function AutomationPage() {
<p className="text-slate-500 text-sm"> . + 1 .</p> <p className="text-slate-500 text-sm"> . + 1 .</p>
</div> </div>
<div className="grid sm:grid-cols-2 gap-6"> <div className="grid sm:grid-cols-2 gap-6">
{premiumTools.map((tool) => ( {premiumTools.map((tool, idx) => (
<div key={tool.id} <div key={tool.id}
className="rounded-2xl overflow-hidden border border-white/10 shadow-xl flex flex-col" className={`rounded-2xl overflow-hidden border border-white/10 shadow-xl flex flex-col reveal reveal-d${idx + 1}`}
style={{ background: `linear-gradient(145deg, ${tool.bgFrom}, ${tool.bgTo})` }}> style={{ background: `linear-gradient(145deg, ${tool.bgFrom}, ${tool.bgTo})` }}>
{/* 카드 헤더 */} {/* 카드 헤더 */}
<div className="p-6 pb-4"> <div className="p-6 pb-4">
@@ -368,15 +406,15 @@ export default function AutomationPage() {
{/* ─── 자동화 툴 무료 다운로드 ─── */} {/* ─── 자동화 툴 무료 다운로드 ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">FREE TOOLS</p> <p className="text-cyan-600 text-xs font-bold uppercase tracking-widest mb-2">FREE TOOLS</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] mb-2"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] mb-2"> </h2>
<p className="text-slate-500 text-sm"> .<br /> .</p> <p className="text-slate-500 text-sm"> .<br /> .</p>
</div> </div>
<div className="grid sm:grid-cols-3 gap-5"> <div className="grid sm:grid-cols-3 gap-5">
{tools.map((tool) => ( {tools.map((tool, idx) => (
<div key={tool.id} style={{ borderColor: tool.borderColor, backgroundColor: tool.bgColor }} <div key={tool.id} style={{ borderColor: tool.borderColor, backgroundColor: tool.bgColor }}
className="rounded-2xl border-2 p-5 flex flex-col relative"> className={`rounded-2xl border-2 p-5 flex flex-col relative reveal reveal-d${idx + 1}`}>
{!tool.ready && ( {!tool.ready && (
<div className="absolute top-3 right-3 bg-slate-200 text-slate-500 text-[10px] font-bold px-2 py-0.5 rounded-full"></div> <div className="absolute top-3 right-3 bg-slate-200 text-slate-500 text-[10px] font-bold px-2 py-0.5 rounded-full"></div>
)} )}
@@ -411,7 +449,7 @@ export default function AutomationPage() {
{/* ─── CTA ─── */} {/* ─── CTA ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<div className="bg-[#012030] rounded-2xl border border-cyan-400/20 p-8 text-center" style={{ 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="bg-[#012030] rounded-2xl border border-cyan-400/20 p-8 text-center reveal" style={{ backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 30px)' }}>
<p className="text-cyan-400 text-xs font-bold uppercase tracking-widest mb-2">FREE CONSULTATION</p> <p className="text-cyan-400 text-xs font-bold uppercase tracking-widest mb-2">FREE CONSULTATION</p>
<h3 className="text-white text-2xl font-extrabold mb-2"> </h3> <h3 className="text-white text-2xl font-extrabold mb-2"> </h3>
<p className="text-cyan-100/40 text-sm mb-6"> </p> <p className="text-cyan-100/40 text-sm mb-6"> </p>

View File

@@ -1,10 +1,32 @@
'use client'; 'use client';
import { useState } 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';
const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null; const KAKAO_CHANNEL_URL = process.env.NEXT_PUBLIC_KAKAO_CHANNEL_URL ?? null;
function useScrollReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
);
el.querySelectorAll('.reveal').forEach((child) => observer.observe(child));
return () => observer.disconnect();
}, []);
return ref;
}
const CHECKLIST = [ const CHECKLIST = [
'주로 어떤 AI 도구를 사용하는지 (ChatGPT / Claude / Gemini)', '주로 어떤 AI 도구를 사용하는지 (ChatGPT / Claude / Gemini)',
'자동화하고 싶은 업무 유형 (이메일 / 보고서 / 코드 등)', '자동화하고 싶은 업무 유형 (이메일 / 보고서 / 코드 등)',
@@ -347,6 +369,7 @@ const examples = [
export default function PromptPage() { export default function PromptPage() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [modalService, setModalService] = useState('프롬프트 엔지니어링'); const [modalService, setModalService] = useState('프롬프트 엔지니어링');
const containerRef = useScrollReveal();
const openModal = (service: string) => { const openModal = (service: string) => {
setModalService(service); setModalService(service);
@@ -354,7 +377,32 @@ export default function PromptPage() {
}; };
return ( return (
<div className="min-h-full bg-[#f0f5ff]"> <div ref={containerRef} className="min-h-full bg-[#f0f5ff]">
<style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
.reveal-d5 { transition-delay: 400ms; }
.prompt-card {
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.prompt-card:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px -12px rgba(0,0,0,0.15);
}
`}</style>
<ContactModal <ContactModal
isOpen={modalOpen} isOpen={modalOpen}
onClose={() => setModalOpen(false)} onClose={() => setModalOpen(false)}
@@ -396,7 +444,7 @@ export default function PromptPage() {
{/* ─── 프리미엄 상품 ─── */} {/* ─── 프리미엄 상품 ─── */}
<div className="px-6 py-12 lg:px-12"> <div className="px-6 py-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<div className="inline-flex items-center gap-2 bg-fuchsia-500/10 border border-fuchsia-500/30 text-fuchsia-400 text-xs font-extrabold px-4 py-1.5 rounded-full uppercase tracking-widest mb-4"> <div className="inline-flex items-center gap-2 bg-fuchsia-500/10 border border-fuchsia-500/30 text-fuchsia-400 text-xs font-extrabold px-4 py-1.5 rounded-full uppercase tracking-widest mb-4">
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor"><path d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /></svg> <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor"><path d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /></svg>
PREMIUM PRODUCTS PREMIUM PRODUCTS
@@ -405,10 +453,10 @@ export default function PromptPage() {
<p className="text-slate-500 text-sm mt-2"> </p> <p className="text-slate-500 text-sm mt-2"> </p>
</div> </div>
<div className="grid lg:grid-cols-2 gap-6"> <div className="grid lg:grid-cols-2 gap-6">
{premiumProducts.map((product) => ( {premiumProducts.map((product, idx) => (
<div <div
key={product.id} key={product.id}
className="rounded-2xl overflow-hidden border" className={`rounded-2xl overflow-hidden border prompt-card reveal reveal-d${(idx % 4) + 1}`}
style={{ borderColor: product.accentBorder, background: `linear-gradient(135deg, ${product.bgFrom}, ${product.bgTo})` }} style={{ borderColor: product.accentBorder, background: `linear-gradient(135deg, ${product.bgFrom}, ${product.bgTo})` }}
> >
{/* 헤더 */} {/* 헤더 */}
@@ -505,13 +553,13 @@ export default function PromptPage() {
{/* ─── Before/After ─── */} {/* ─── Before/After ─── */}
<div className="px-6 py-12 lg:px-12"> <div className="px-6 py-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<p className="text-violet-600 text-xs font-bold uppercase tracking-widest mb-2">BEFORE vs AFTER</p> <p className="text-violet-600 text-xs font-bold uppercase tracking-widest mb-2">BEFORE vs AFTER</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div> </div>
<div className="space-y-5"> <div className="space-y-5">
{examples.map((ex) => ( {examples.map((ex, idx) => (
<div key={ex.type} className="bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden"> <div key={ex.type} className={`bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden reveal reveal-d${idx + 1}`}>
<div className="bg-[#04102b] px-5 py-3 flex items-center justify-between"> <div className="bg-[#04102b] px-5 py-3 flex items-center justify-between">
<span className="text-white/60 text-xs font-semibold font-mono">{ex.type} </span> <span className="text-white/60 text-xs font-semibold font-mono">{ex.type} </span>
<span className="bg-violet-400/20 border border-violet-400/30 text-violet-300 text-xs px-3 py-1 rounded-full">{ex.improvement}</span> <span className="bg-violet-400/20 border border-violet-400/30 text-violet-300 text-xs px-3 py-1 rounded-full">{ex.improvement}</span>
@@ -543,13 +591,13 @@ export default function PromptPage() {
{/* ─── 활용 분야 ─── */} {/* ─── 활용 분야 ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<p className="text-violet-600 text-xs font-bold uppercase tracking-widest mb-2">USE CASES</p> <p className="text-violet-600 text-xs font-bold uppercase tracking-widest mb-2">USE CASES</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div> </div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{useCases.map((uc, i) => ( {useCases.map((uc, i) => (
<div key={uc.label} className="bg-white rounded-2xl border border-[#dbe8ff] p-5 hover:border-violet-200 transition-colors"> <div key={uc.label} className={`bg-white rounded-2xl border border-[#dbe8ff] p-5 hover:border-violet-200 transition-all duration-300 reveal reveal-d${(i % 3) + 1}`}>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-lg bg-violet-50 border border-violet-200 flex items-center justify-center flex-shrink-0"> <div className="w-8 h-8 rounded-lg bg-violet-50 border border-violet-200 flex items-center justify-center flex-shrink-0">
<span className="text-violet-600 font-extrabold text-xs">{String(i + 1).padStart(2, '0')}</span> <span className="text-violet-600 font-extrabold text-xs">{String(i + 1).padStart(2, '0')}</span>
@@ -568,13 +616,13 @@ export default function PromptPage() {
{/* ─── 요금제 ─── */} {/* ─── 요금제 ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="text-center mb-8"> <div className="text-center mb-8 reveal">
<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]"></h2> <h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"></h2>
</div> </div>
<div className="grid sm:grid-cols-3 gap-5"> <div className="grid sm:grid-cols-3 gap-5">
{plans.map((plan) => ( {plans.map((plan, idx) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col ${ <div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col reveal reveal-d${idx + 1} ${
plan.highlight plan.highlight
? 'bg-gradient-to-br from-[#0d0a2e] to-[#1a0f5c] border-violet-400/30 shadow-2xl shadow-violet-900/20 scale-105' ? 'bg-gradient-to-br from-[#0d0a2e] to-[#1a0f5c] border-violet-400/30 shadow-2xl shadow-violet-900/20 scale-105'
: 'bg-white border-[#dbe8ff]' : 'bg-white border-[#dbe8ff]'
@@ -615,7 +663,7 @@ export default function PromptPage() {
{/* ─── CTA ─── */} {/* ─── CTA ─── */}
<div className="px-6 pb-12 lg:px-12"> <div className="px-6 pb-12 lg:px-12">
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<div className="bg-gradient-to-r from-[#0d0a2e] to-[#1a0f5c] rounded-2xl border border-violet-400/20 p-8 text-center"> <div className="bg-gradient-to-r from-[#0d0a2e] to-[#1a0f5c] rounded-2xl border border-violet-400/20 p-8 text-center reveal">
<p className="text-violet-400 text-xs font-bold uppercase tracking-widest mb-2">GET STARTED</p> <p className="text-violet-400 text-xs font-bold uppercase tracking-widest mb-2">GET STARTED</p>
<h3 className="text-white text-2xl font-extrabold mb-2">AI를 </h3> <h3 className="text-white text-2xl font-extrabold mb-2">AI를 </h3>
<p className="text-violet-100/40 text-sm mb-6"> </p> <p className="text-violet-100/40 text-sm mb-6"> </p>

16
app/tools/layout.tsx Normal file
View File

@@ -0,0 +1,16 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '여긴 뭐 만들어요? — 자동화 도구 쇼케이스 | 쟁승메이드',
description:
'실제 고객 프로젝트 기반 자동화 도구를 직접 체험해보세요. 이베이 부품 AI 리스팅, 네이버 블로그 자동화 등 완성형 데모를 무료로 제공합니다.',
openGraph: {
title: '여긴 뭐 만들어요? — 자동화 도구 쇼케이스',
description:
'수작업 30분 → 10초. 실제로 작동하는 자동화 도구를 직접 체험해보세요.',
},
};
export default function ToolsLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -1,7 +1,17 @@
'use client'; 'use client';
import { useEffect, useRef } from 'react';
import Link from 'next/link'; import Link from 'next/link';
/* ═══════════════════════════════════════════════════
도구 쇼케이스 — 리디자인 v2
설계 원칙:
1. 홈 페이지 에디토리얼 톤 계승 — 증거 중심, 텍스트 우선
2. Supanova: 비대칭 레이아웃, 스크롤 애니메이션, 프리미엄 카드
3. 사이트 디자인 시스템 완전 통일 (라이트 bg + 다크 카드)
4. 실제 수치와 체험 유도 — 전환율 중심 구조
═══════════════════════════════════════════════════ */
interface ToolCard { interface ToolCard {
id: string; id: string;
title: string; title: string;
@@ -10,8 +20,10 @@ interface ToolCard {
tags: string[]; tags: string[];
href: string; href: string;
status: 'live' | 'beta' | 'coming'; status: 'live' | 'beta' | 'coming';
icon: React.ReactNode;
gradient: string; gradient: string;
iconPath: string;
metric: { value: string; label: string };
highlight: string;
} }
const TOOLS: ToolCard[] = [ const TOOLS: ToolCard[] = [
@@ -20,138 +32,347 @@ const TOOLS: ToolCard[] = [
title: '이베이 부품 AI 리스팅', title: '이베이 부품 AI 리스팅',
subtitle: 'eBay Auto Parts Listing Tool', subtitle: 'eBay Auto Parts Listing Tool',
description: description:
'품번 하나 입력하면 AI가 RockAuto·eBay를 크롤링하고, 리스팅 제목·Fitment·관세까지 자동 생성합니다. 수작업 30분 → 10초.', '품번 하나 입력하면 AI가 RockAuto·eBay를 크롤링하고, 리스팅 제목·Fitment·관세까지 자동 생성합니다.',
tags: ['크롤링', 'Claude AI', '관세 계산', 'eBay Motors'], tags: ['크롤링', 'Claude AI', '관세 계산', 'eBay Motors'],
href: '/tools/ebay-parts', href: '/tools/ebay-parts',
status: 'live', status: 'live',
icon: (
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 11h4m-2-2v4" />
</svg>
),
gradient: 'from-blue-600 to-cyan-500', gradient: 'from-blue-600 to-cyan-500',
iconPath: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
metric: { value: '10초', label: '30분 작업 → 10초로 단축' },
highlight: '수작업 대비 180배 빠름',
}, },
{ {
id: 'naver-blog', id: 'naver-blog',
title: '네이버 블로그 자동화', title: '네이버 블로그 자동화',
subtitle: 'Naver Blog AI Writer', subtitle: 'Naver Blog AI Writer',
description: description:
'주제·톤·분량만 선택하면 AI가 SEO 최적화된 블로그 글을 자동 작성합니다. 소제목 구조, 이미지 배치 가이드까지 한 번에.', '주제·톤·분량만 선택하면 AI가 SEO 최적화된 블로그 글을 자동 작성합니다. 소제목 구조, 이미지 배치 가이드까지.',
tags: ['GPT/Claude', 'SEO 최적화', '자동 포스팅', '이미지 가이드'], tags: ['GPT/Claude', 'SEO 최적화', '자동 포스팅', '이미지 가이드'],
href: '/tools/naver-blog', href: '/tools/naver-blog',
status: 'live', status: 'live',
icon: (
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
),
gradient: 'from-emerald-600 to-teal-500', gradient: 'from-emerald-600 to-teal-500',
iconPath: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z',
metric: { value: '3분', label: '1시간 글쓰기 → 3분 자동 완성' },
highlight: 'SEO 최적화 자동 포함',
}, },
]; ];
const STATUS_BADGE: Record<string, { label: string; className: string }> = { const STATUS_BADGE: Record<string, { label: string; className: string }> = {
live: { label: '체험 가능', className: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30' }, live: { label: '체험 가능', className: 'bg-emerald-50 text-emerald-700 border-emerald-200' },
beta: { label: 'BETA', className: 'bg-amber-500/15 text-amber-400 border-amber-500/30' }, beta: { label: 'BETA', className: 'bg-amber-50 text-amber-700 border-amber-200' },
coming: { label: '준비 중', className: 'bg-slate-500/15 text-slate-400 border-slate-500/30' }, coming: { label: '준비 중', className: 'bg-slate-100 text-slate-500 border-slate-200' },
}; };
const PROCESS_STEPS = [
{ step: '01', title: '문제 정의', desc: '고객의 반복 업무를 분석합니다' },
{ step: '02', title: '자동화 설계', desc: 'AI + 크롤링 + API 조합을 설계합니다' },
{ step: '03', title: '프로토타입', desc: '실제 데이터로 동작하는 MVP를 만듭니다' },
{ step: '04', title: '체험 배포', desc: '이 페이지에 데모를 올려 직접 테스트합니다' },
];
function useScrollReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.15, rootMargin: '0px 0px -40px 0px' }
);
const children = el.querySelectorAll('.reveal');
children.forEach((child) => observer.observe(child));
return () => observer.disconnect();
}, []);
return ref;
}
export default function ToolsShowcasePage() { export default function ToolsShowcasePage() {
const containerRef = useScrollReveal();
return ( return (
<div className="min-h-screen"> <div ref={containerRef} className="min-h-screen">
{/* Hero */} {/* ── Scroll-reveal animation styles ── */}
<section className="px-6 pt-12 pb-10 max-w-5xl mx-auto text-center"> <style>{`
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400 text-xs font-medium mb-6"> .reveal {
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> opacity: 0;
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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" /> transform: translateY(1.5rem);
</svg> 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-delay-1 { transition-delay: 80ms; }
.reveal-delay-2 { transition-delay: 160ms; }
.reveal-delay-3 { transition-delay: 240ms; }
.reveal-delay-4 { transition-delay: 320ms; }
.tool-card {
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.tool-card:hover {
transform: translateY(-6px);
box-shadow: 0 24px 48px -12px rgba(26, 86, 219, 0.12),
0 8px 16px -4px rgba(0, 0, 0, 0.06);
}
.metric-pulse {
animation: pulse-ring 2.5s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@keyframes pulse-ring {
0%, 100% { box-shadow: 0 0 0 0 rgba(26, 86, 219, 0.15); }
50% { box-shadow: 0 0 0 12px rgba(26, 86, 219, 0); }
}
.arrow-shift {
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.group:hover .arrow-shift {
transform: translateX(4px);
}
`}</style>
{/* ═══════════════════════════════════════════
HERO — 좌측 텍스트 / 우측 수치 비대칭 레이아웃
═══════════════════════════════════════════ */}
<section className="px-6 pt-10 pb-16 max-w-5xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-10 items-center">
{/* 좌측: 텍스트 블록 */}
<div className="lg:col-span-3 reveal">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-blue-50 border border-blue-200 text-blue-700 text-xs font-medium mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
</div>
<h1 className="text-3xl md:text-4xl font-bold text-slate-900 leading-tight mb-4" style={{ wordBreak: 'keep-all' as const }}>
?
</h1>
<p className="text-slate-500 text-base md:text-lg leading-relaxed max-w-lg" style={{ wordBreak: 'keep-all' as const }}>
&ldquo; ?&rdquo; .
<span className="text-slate-800 font-semibold"> </span> .
</p>
</div>
{/* 우측: 수치 카드 */}
<div className="lg:col-span-2 reveal reveal-delay-1">
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm">
<div className="grid grid-cols-2 gap-4">
<div className="text-center p-3">
<div className="text-2xl font-bold text-blue-600">{TOOLS.filter(t => t.status === 'live').length}</div>
<div className="text-slate-400 text-xs mt-1"> </div>
</div>
<div className="text-center p-3">
<div className="text-2xl font-bold text-emerald-600">180x</div>
<div className="text-slate-400 text-xs mt-1"> </div>
</div>
<div className="text-center p-3">
<div className="text-2xl font-bold text-violet-600"></div>
<div className="text-slate-400 text-xs mt-1"> </div>
</div>
<div className="text-center p-3">
<div className="text-2xl font-bold text-cyan-600"></div>
<div className="text-slate-400 text-xs mt-1"> </div>
</div>
</div>
</div>
</div>
</div> </div>
<h1 className="text-3xl md:text-4xl font-bold text-white leading-tight mb-4">
?
</h1>
<p className="text-slate-400 text-base md:text-lg max-w-2xl mx-auto leading-relaxed">
&ldquo; ?&rdquo; .<br className="hidden md:block" />
<span className="text-white font-medium"> </span> .
</p>
</section> </section>
{/* Tool Cards */} {/* ═══════════════════════════════════════════
TOOL CARDS — 피처드 카드 (풀와이드) 패턴
═══════════════════════════════════════════ */}
<section className="px-6 pb-16 max-w-5xl mx-auto"> <section className="px-6 pb-16 max-w-5xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="flex flex-col gap-6">
{TOOLS.map((tool) => { {TOOLS.map((tool, idx) => {
const badge = STATUS_BADGE[tool.status]; const badge = STATUS_BADGE[tool.status];
const isEven = idx % 2 === 1;
return ( return (
<Link <Link
key={tool.id} key={tool.id}
href={tool.href} href={tool.href}
className="group block bg-slate-900/80 rounded-2xl border border-slate-700/50 hover:border-slate-600/80 transition-all duration-200 overflow-hidden" className={`group block tool-card bg-white rounded-2xl border border-slate-200 overflow-hidden active:scale-[0.99] reveal reveal-delay-${idx + 1}`}
> >
{/* Gradient header */} <div className={`flex flex-col ${isEven ? 'md:flex-row-reverse' : 'md:flex-row'}`}>
<div className={`h-2 bg-gradient-to-r ${tool.gradient}`} /> {/* 좌측 (또는 우측): 메트릭 비주얼 */}
<div className={`relative flex-shrink-0 md:w-64 lg:w-72 p-4 md:p-8 flex items-center md:flex-col md:justify-center bg-gradient-to-br ${tool.gradient} text-white`}>
<div className="p-6"> {/* 배경 패턴 (모바일 숨김) */}
{/* Icon + Badge */} <div className="absolute inset-0 opacity-10 hidden md:block">
<div className="flex items-start justify-between mb-4"> <div className="absolute top-4 right-4 w-32 h-32 border border-white/30 rounded-full" />
<div className={`w-12 h-12 rounded-xl bg-gradient-to-br ${tool.gradient} flex items-center justify-center text-white`}> <div className="absolute bottom-4 left-4 w-20 h-20 border border-white/20 rounded-full" />
{tool.icon} </div>
{/* 모바일: 수평 compact / 데스크톱: 수직 */}
<div className="relative z-10 flex items-center gap-4 md:flex-col md:text-center">
<div className="w-11 h-11 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-white/20 flex items-center justify-center flex-shrink-0 border border-white/10 shadow-[inset_0_1px_0_rgba(255,255,255,0.2)]">
<svg className="w-5 h-5 md:w-7 md:h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d={tool.iconPath} />
</svg>
</div>
<div className="flex items-center gap-3 md:flex-col">
<div className="metric-pulse inline-flex items-center justify-center w-14 h-14 md:w-20 md:h-20 rounded-full bg-white/15 border border-white/20">
<span className="text-lg md:text-2xl font-bold">{tool.metric.value}</span>
</div>
<p className="text-white/80 text-xs leading-relaxed">{tool.metric.label}</p>
</div>
</div> </div>
<span className={`px-2.5 py-0.5 rounded-full text-[11px] font-medium border ${badge.className}`}>
{badge.label}
</span>
</div> </div>
{/* Title */} {/* 우측 (또는 좌측): 콘텐츠 */}
<h3 className="text-white font-bold text-lg mb-0.5 group-hover:text-blue-300 transition-colors"> <div className="flex-1 p-6 md:p-8 flex flex-col justify-between">
{tool.title} <div>
</h3> {/* 상단: 배지 + 하이라이트 */}
<p className="text-slate-500 text-xs font-mono mb-3">{tool.subtitle}</p> <div className="flex items-center gap-2 mb-4 flex-wrap">
<span className={`px-2.5 py-0.5 rounded-full text-[11px] font-semibold border ${badge.className}`}>
{badge.label}
</span>
<span className="px-2.5 py-0.5 rounded-full text-[11px] font-medium bg-blue-50 text-blue-600 border border-blue-100">
{tool.highlight}
</span>
</div>
{/* Description */} {/* 제목 */}
<p className="text-slate-400 text-sm leading-relaxed mb-4"> <h3 className="text-xl font-bold text-slate-900 mb-1 group-hover:text-blue-700 transition-colors duration-300">
{tool.description} {tool.title}
</p> </h3>
<p className="text-slate-400 text-xs font-mono mb-3 tracking-wide">{tool.subtitle}</p>
{/* Tags */} {/* 설명 */}
<div className="flex flex-wrap gap-1.5 mb-5"> <p className="text-slate-500 text-sm leading-relaxed mb-5" style={{ wordBreak: 'keep-all' as const }}>
{tool.tags.map((tag) => ( {tool.description}
<span </p>
key={tag}
className="px-2 py-0.5 rounded bg-slate-800 text-slate-400 text-[11px] font-medium"
>
{tag}
</span>
))}
</div>
{/* CTA */} {/* 태그 */}
<div className="flex items-center gap-2 text-blue-400 text-sm font-medium group-hover:gap-3 transition-all"> <div className="flex flex-wrap gap-1.5 mb-5">
{tool.tags.map((tag) => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <span
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" /> key={tag}
</svg> className="px-2.5 py-1 rounded-lg bg-slate-50 text-slate-500 text-[11px] font-medium border border-slate-100"
>
{tag}
</span>
))}
</div>
</div>
{/* CTA */}
<div className="flex items-center gap-2 text-blue-600 text-sm font-semibold">
<svg className="w-4 h-4 arrow-shift" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</div>
</div> </div>
</div> </div>
</Link> </Link>
); );
})} })}
</div> </div>
</section>
{/* Bottom CTA */} {/* ═══════════════════════════════════════════
<div className="mt-12 text-center bg-slate-900/60 rounded-2xl border border-slate-700/40 p-8"> PROCESS — 어떻게 만들어지나? (수직 타임라인)
<h3 className="text-white font-bold text-lg mb-2"> ?</h3> ═══════════════════════════════════════════ */}
<p className="text-slate-400 text-sm mb-5"> <section className="px-6 pb-16 max-w-5xl mx-auto">
. . <div className="reveal mb-10">
</p> <h2 className="text-2xl font-bold text-slate-900 mb-2"> </h2>
<Link <p className="text-slate-400 text-sm"> 4</p>
href="/freelance#contact-form" </div>
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-blue-600 hover:bg-blue-500 text-white text-sm font-semibold transition-colors"
> <div className="grid grid-cols-1 md:grid-cols-4 gap-0 md:gap-6">
{PROCESS_STEPS.map((item, idx) => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <div key={item.step} className={`reveal reveal-delay-${idx + 1} relative`}>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" /> {/* 스텝 카드 */}
</svg> <div className="bg-white rounded-xl border border-slate-200 p-5 h-full hover:border-blue-200 transition-colors duration-300">
</Link> <div className="flex items-center gap-3 mb-3">
<span className="w-8 h-8 rounded-lg bg-blue-50 text-blue-600 text-xs font-bold flex items-center justify-center border border-blue-100">
{item.step}
</span>
{idx < PROCESS_STEPS.length - 1 && (
<div className="hidden md:block absolute -right-3 top-1/2 -translate-y-1/2 z-10">
<svg className="w-6 h-6 text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 5l7 7-7 7" />
</svg>
</div>
)}
</div>
<h4 className="text-slate-900 font-bold text-sm mb-1">{item.title}</h4>
<p className="text-slate-400 text-xs leading-relaxed">{item.desc}</p>
</div>
</div>
))}
</div>
</section>
{/* ═══════════════════════════════════════════
BOTTOM CTA — 풀블리드 전환 섹션
═══════════════════════════════════════════ */}
<section className="px-6 pb-16 max-w-5xl mx-auto">
<div className="reveal relative overflow-hidden rounded-2xl bg-slate-900 p-8 md:p-12">
{/* 배경 데코 */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute -top-20 -right-20 w-60 h-60 bg-blue-500/10 rounded-full blur-3xl" />
<div className="absolute -bottom-20 -left-20 w-48 h-48 bg-violet-500/10 rounded-full blur-3xl" />
</div>
<div className="relative z-10 flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div>
<h3 className="text-white font-bold text-xl md:text-2xl mb-2" style={{ wordBreak: 'keep-all' as const }}>
?
</h3>
<p className="text-slate-400 text-sm leading-relaxed max-w-lg" style={{ wordBreak: 'keep-all' as const }}>
. , .
</p>
</div>
<Link
href="/freelance#contact-form"
className="group inline-flex items-center gap-3 px-7 py-4 rounded-xl bg-blue-600 hover:bg-blue-500 active:scale-[0.98] text-white text-sm font-bold transition-all duration-300 shadow-lg shadow-blue-600/20 hover:shadow-blue-500/30 flex-shrink-0"
>
<span className="w-7 h-7 rounded-full bg-white/15 flex items-center justify-center flex-shrink-0">
<svg className="w-3.5 h-3.5 arrow-shift" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</span>
</Link>
</div>
{/* 하단 신뢰 요소 */}
<div className="relative z-10 mt-8 pt-6 border-t border-slate-700/50 flex flex-wrap gap-x-8 gap-y-2">
<div className="flex items-center gap-2 text-slate-500 text-xs">
<svg className="w-3.5 h-3.5 text-emerald-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="flex items-center gap-2 text-slate-500 text-xs">
<svg className="w-3.5 h-3.5 text-emerald-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="flex items-center gap-2 text-slate-500 text-xs">
<svg className="w-3.5 h-3.5 text-emerald-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div> </div>
</section> </section>
</div> </div>