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

@@ -1,9 +1,31 @@
'use client';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
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 = [
{
id: 'excel',
@@ -163,6 +185,7 @@ const process = [
export default function AutomationPage() {
const [modalOpen, setModalOpen] = useState(false);
const [modalService, setModalService] = useState('업무 자동화');
const containerRef = useScrollReveal();
const openModal = (service: string) => {
setModalService(service);
@@ -170,7 +193,22 @@ export default function AutomationPage() {
};
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
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
@@ -212,13 +250,13 @@ export default function AutomationPage() {
{/* ─── 자동화 유형 ─── */}
<div className="px-6 py-12 lg:px-12">
<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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{automationTypes.map((at) => (
<div key={at.title} className={`bg-white rounded-2xl border-2 ${at.accentColor} p-5`}>
{automationTypes.map((at, idx) => (
<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>
<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>
@@ -239,15 +277,15 @@ export default function AutomationPage() {
{/* ─── 프로세스 ─── */}
<div className="px-6 pb-12 lg:px-12">
<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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="relative">
<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">
{process.map((p) => (
<div key={p.step} className="relative text-center">
{process.map((p, idx) => (
<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">
<span className="text-cyan-400 text-xs font-bold">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="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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="grid sm:grid-cols-3 gap-5">
{plans.map((plan) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col ${
{plans.map((plan, idx) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col reveal reveal-d${idx + 1} ${
plan.highlight
? 'bg-[#012030] border-cyan-400/30 shadow-2xl shadow-cyan-900/20 scale-105'
: 'bg-white border-[#dbe8ff]'
@@ -304,7 +342,7 @@ export default function AutomationPage() {
{/* ─── 프리미엄 툴 ─── */}
<div className="px-6 pb-4 lg:px-12">
<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">
<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
@@ -313,9 +351,9 @@ export default function AutomationPage() {
<p className="text-slate-500 text-sm"> . + 1 .</p>
</div>
<div className="grid sm:grid-cols-2 gap-6">
{premiumTools.map((tool) => (
{premiumTools.map((tool, idx) => (
<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})` }}>
{/* 카드 헤더 */}
<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="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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b] mb-2"> </h2>
<p className="text-slate-500 text-sm"> .<br /> .</p>
</div>
<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 }}
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 && (
<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 ─── */}
<div className="px-6 pb-12 lg:px-12">
<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>
<h3 className="text-white text-2xl font-extrabold mb-2"> </h3>
<p className="text-cyan-100/40 text-sm mb-6"> </p>

View File

@@ -1,10 +1,32 @@
'use client';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import ContactModal from '../../components/ContactModal';
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 = [
'주로 어떤 AI 도구를 사용하는지 (ChatGPT / Claude / Gemini)',
'자동화하고 싶은 업무 유형 (이메일 / 보고서 / 코드 등)',
@@ -347,6 +369,7 @@ const examples = [
export default function PromptPage() {
const [modalOpen, setModalOpen] = useState(false);
const [modalService, setModalService] = useState('프롬프트 엔지니어링');
const containerRef = useScrollReveal();
const openModal = (service: string) => {
setModalService(service);
@@ -354,7 +377,32 @@ export default function PromptPage() {
};
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
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
@@ -396,7 +444,7 @@ export default function PromptPage() {
{/* ─── 프리미엄 상품 ─── */}
<div className="px-6 py-12 lg:px-12">
<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">
<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
@@ -405,10 +453,10 @@ export default function PromptPage() {
<p className="text-slate-500 text-sm mt-2"> </p>
</div>
<div className="grid lg:grid-cols-2 gap-6">
{premiumProducts.map((product) => (
{premiumProducts.map((product, idx) => (
<div
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})` }}
>
{/* 헤더 */}
@@ -505,13 +553,13 @@ export default function PromptPage() {
{/* ─── Before/After ─── */}
<div className="px-6 py-12 lg:px-12">
<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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="space-y-5">
{examples.map((ex) => (
<div key={ex.type} className="bg-white rounded-2xl border border-[#dbe8ff] overflow-hidden">
{examples.map((ex, idx) => (
<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">
<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>
@@ -543,13 +591,13 @@ export default function PromptPage() {
{/* ─── 활용 분야 ─── */}
<div className="px-6 pb-12 lg:px-12">
<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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"> </h2>
</div>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{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="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>
@@ -568,13 +616,13 @@ export default function PromptPage() {
{/* ─── 요금제 ─── */}
<div className="px-6 pb-12 lg:px-12">
<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>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]"></h2>
</div>
<div className="grid sm:grid-cols-3 gap-5">
{plans.map((plan) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col ${
{plans.map((plan, idx) => (
<div key={plan.name} className={`rounded-2xl border p-6 relative flex flex-col reveal reveal-d${idx + 1} ${
plan.highlight
? 'bg-gradient-to-br from-[#0d0a2e] to-[#1a0f5c] border-violet-400/30 shadow-2xl shadow-violet-900/20 scale-105'
: 'bg-white border-[#dbe8ff]'
@@ -615,7 +663,7 @@ export default function PromptPage() {
{/* ─── CTA ─── */}
<div className="px-6 pb-12 lg:px-12">
<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>
<h3 className="text-white text-2xl font-extrabold mb-2">AI를 </h3>
<p className="text-violet-100/40 text-sm mb-6"> </p>