269 lines
11 KiB
TypeScript
269 lines
11 KiB
TypeScript
import { useState } from "react"
|
|
import { AnimatePresence, motion } from "motion/react"
|
|
import { Bot, Check, Monitor, Smartphone, Sparkles, type LucideIcon } from "lucide-react"
|
|
|
|
import { BrowserFrame, PhoneFrame } from "@/components/ui/device-mockups"
|
|
import { Section } from "@/components/ui/section"
|
|
import { SectionHeading } from "@/components/ui/section-heading"
|
|
import { Typography } from "@/components/ui/typography"
|
|
|
|
/** 3단계 이용 가이드 — 탭 전환식 GIF 시연 (데스크톱/모바일 목업). */
|
|
export function HowItWorksDemo() {
|
|
const [activeKey, setActiveKey] = useState<DemoTab["key"]>("create")
|
|
|
|
const activeTab = DEMO_TABS.find((tab) => tab.key === activeKey)!
|
|
|
|
return (
|
|
<Section id="how-it-works-demo" width="lg" bordered className="overflow-hidden">
|
|
<SectionHeading
|
|
align="center"
|
|
className="mb-20"
|
|
eyebrow="SERVICE DEMONSTRATION"
|
|
title="견적 생성부터 AI 협상까지 한눈에 보기"
|
|
description="복잡해 보이는 구매 과정이 어떻게 자동화되는지 실제 작동 화면(GIF)을 통해 쉽고 직관적으로 확인해 보세요."
|
|
descriptionClassName="text-[17px] max-w-2xl"
|
|
/>
|
|
|
|
{/* 탭 스위치 */}
|
|
<div className="flex justify-center mb-16">
|
|
<div className="p-1 bg-fill rounded-[14px] inline-flex flex-wrap justify-center gap-1">
|
|
{DEMO_TABS.map((tab) => (
|
|
<button
|
|
key={tab.key}
|
|
onClick={() => setActiveKey(tab.key)}
|
|
className={`px-5 py-2.5 rounded-[10px] text-xs sm:text-sm font-bold transition-all flex items-center gap-2 cursor-pointer ${
|
|
activeKey === tab.key ? "bg-white text-primary" : "text-ink-soft hover:text-ink"
|
|
}`}
|
|
>
|
|
<tab.tabIcon className="w-4 h-4" />
|
|
<span>{tab.tabLabel}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center min-h-[500px]">
|
|
{/* 좌: 단계 설명 */}
|
|
<div className="lg:col-span-5 space-y-6">
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={`${activeTab.key}-text`}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: 20 }}
|
|
transition={{ duration: 0.4 }}
|
|
className="space-y-6"
|
|
>
|
|
<div className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold ${activeTab.badge.className}`}>
|
|
<activeTab.badge.icon className="w-3.5 h-3.5" />
|
|
{activeTab.badge.label}
|
|
</div>
|
|
<Typography variant="heading">{activeTab.heading}</Typography>
|
|
{activeTab.paragraphs.map((paragraph, idx) => (
|
|
<Typography key={idx} variant="body">
|
|
{paragraph}
|
|
</Typography>
|
|
))}
|
|
|
|
<div className="border-t border-line pt-5 mt-2 space-y-3">
|
|
{activeTab.checks.map((check, idx) => (
|
|
<div key={idx} className="flex items-center gap-2 text-xs font-bold text-ink-soft">
|
|
<Check className={`w-4 h-4 ${activeTab.checkClassName}`} />
|
|
<span>{check}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
{/* 우: 디바이스 목업 + GIF */}
|
|
<div className="lg:col-span-7 flex justify-center items-center">
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={`${activeTab.key}-mockup`}
|
|
initial={{ opacity: 0, scale: 0.96, y: 15 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.96, y: -15 }}
|
|
transition={{ duration: 0.4 }}
|
|
className={`w-full ${activeTab.mockup === "phone" ? "flex justify-center" : ""}`}
|
|
>
|
|
{activeTab.mockup === "browser" ? (
|
|
<BrowserFrame url={activeTab.url!}>
|
|
<GifSlot tab={activeTab} />
|
|
</BrowserFrame>
|
|
) : (
|
|
<PhoneFrame>
|
|
<GifSlot tab={activeTab} />
|
|
</PhoneFrame>
|
|
)}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
</Section>
|
|
)
|
|
}
|
|
|
|
/** GIF 로드 실패 시(파일 미배치) 배치 안내 폴백을 보여주는 슬롯. */
|
|
function GifSlot({ tab }: { tab: DemoTab }) {
|
|
const [error, setError] = useState(false)
|
|
|
|
if (error) {
|
|
const phone = tab.mockup === "phone"
|
|
return (
|
|
<div className={phone ? "text-center p-6 space-y-4 w-full" : "text-center p-8 max-w-sm space-y-4"}>
|
|
<div className={`${tab.fallback.iconWrapClassName} flex items-center justify-center mx-auto animate-pulse`}>
|
|
<tab.fallback.icon className={phone ? "w-6 h-6" : "w-7 h-7"} />
|
|
</div>
|
|
<div>
|
|
<h4 className={`${phone ? "text-xs" : "text-sm"} font-bold text-ink break-keep`}>{tab.fallback.title}</h4>
|
|
<p className={`${phone ? "text-[10px] px-2" : "text-[11px]"} text-ink-muted font-semibold mt-1 leading-relaxed break-keep`}>
|
|
프로젝트 루트의{" "}
|
|
<code className={`px-1.5 py-0.5 rounded border ${tab.fallback.codeClassName}`}>public{tab.gif}</code> 경로에{" "}
|
|
{tab.fallback.hint}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<img
|
|
src={tab.gif}
|
|
alt={tab.gifAlt}
|
|
onError={() => setError(true)}
|
|
referrerPolicy="no-referrer"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
)
|
|
}
|
|
|
|
type DemoTab = {
|
|
key: "create" | "negotiate" | "result"
|
|
tabIcon: LucideIcon
|
|
tabLabel: string
|
|
badge: { icon: LucideIcon; label: string; className: string }
|
|
checkClassName: string
|
|
heading: React.ReactNode
|
|
paragraphs: React.ReactNode[]
|
|
checks: string[]
|
|
mockup: "browser" | "phone"
|
|
url?: string
|
|
gif: string
|
|
gifAlt: string
|
|
fallback: {
|
|
icon: LucideIcon
|
|
iconWrapClassName: string
|
|
codeClassName: string
|
|
title: string
|
|
hint: string
|
|
}
|
|
}
|
|
|
|
const DEMO_TABS: DemoTab[] = [
|
|
{
|
|
key: "create",
|
|
tabIcon: Monitor,
|
|
tabLabel: "1단계: AI 견적 & 가이드 수립",
|
|
badge: { icon: Sparkles, label: "구매 관리자 콘솔 (웹)", className: "bg-primary-soft text-primary" },
|
|
checkClassName: "text-primary",
|
|
heading: (
|
|
<>
|
|
엑셀 등록만으로 끝나는 <br />
|
|
초정밀 가이드라인 자율 수립
|
|
</>
|
|
),
|
|
paragraphs: [
|
|
<>
|
|
품목 명과 시중 시장 가격을 입력하면, negotium의 지능형 <b>LPS(Lowest Price Scanning)</b> 기술이 실시간 시세를 자동
|
|
연동합니다.
|
|
</>,
|
|
<>
|
|
과거 거래 데이터 분석을 바탕으로, 파트너사가 반발심을 가질 확률을 최저로 억제하면서 사내 마진율을 방어할 수 있는{" "}
|
|
<b className="text-primary">최적 목표 단가(Target Price)</b> 가이드라인을 기획해냅니다.
|
|
</>,
|
|
],
|
|
checks: ["원자재 및 공정 시장 실시간 시세 연동", "이탈 저항 임계 모델 기반 가이드 자동 완성"],
|
|
mockup: "browser",
|
|
url: "https://console.negotium.ai/estimates/new",
|
|
gif: "/gifs/quote_generation.gif",
|
|
gifAlt: "견적 생성 가이드라인 수립 시연",
|
|
fallback: {
|
|
icon: Monitor,
|
|
iconWrapClassName: "w-14 h-14 bg-primary-soft rounded-2xl text-primary",
|
|
codeClassName: "bg-white text-primary",
|
|
title: "견적 생성 시연 GIF 공간",
|
|
hint: "실제 기동 GIF를 배치하시면 이 영역에 자동으로 재생됩니다.",
|
|
},
|
|
},
|
|
{
|
|
key: "negotiate",
|
|
tabIcon: Smartphone,
|
|
tabLabel: "2단계: AI 자동 밀당 협상",
|
|
badge: { icon: Bot, label: "협력사 모바일 포털 (모바일)", className: "bg-emerald-50 border border-emerald-100 text-positive" },
|
|
checkClassName: "text-emerald-500",
|
|
heading: (
|
|
<>
|
|
로그인도 필요 없이 <br />
|
|
전용 링크로 간편하게 밀당 조율
|
|
</>
|
|
),
|
|
paragraphs: [
|
|
<>
|
|
각 파트너사 담당자는 번거로운 가입 절차 없이 발송된 <b>비대면 협상 모바일 포털</b>에 접속해 단가를 실시간 조율합니다.
|
|
</>,
|
|
<>
|
|
단순 마진 깎기가 아닌, <b>"물량 보증"</b> 혹은 <b>"지불 주기 단축"</b> 등의 와일드카드 거래 카드를 연동 제안하여
|
|
파트너사의 자발적인 마진 타협을 부드럽게 이끌어냅니다.
|
|
</>,
|
|
],
|
|
checks: ["1:1 프라이빗 대화형 역경매 포털 제공", "불필요한 실랑이를 예방하여 파트너십 보호"],
|
|
mockup: "phone",
|
|
gif: "/gifs/negotiation.gif",
|
|
gifAlt: "AI 모바일 자동 협상 시연",
|
|
fallback: {
|
|
icon: Smartphone,
|
|
iconWrapClassName: "w-12 h-12 bg-emerald-50 border border-emerald-100 rounded-full text-emerald-500",
|
|
codeClassName: "bg-fill text-positive",
|
|
title: "협상 시연 GIF 공간",
|
|
hint: "실제 모바일 협상 시뮬레이션 GIF를 배치해 주세요.",
|
|
},
|
|
},
|
|
{
|
|
key: "result",
|
|
tabIcon: Monitor,
|
|
tabLabel: "3단계: AI 협상 결과 확인",
|
|
badge: { icon: Monitor, label: "구매 관리자 콘솔 (웹)", className: "bg-purple-50 border border-purple-100 text-purple-600" },
|
|
checkClassName: "text-purple-500",
|
|
heading: (
|
|
<>
|
|
자동 분석 및 타결 보고서 <br />
|
|
대시보드에서 성과 확인
|
|
</>
|
|
),
|
|
paragraphs: [
|
|
<>
|
|
협상이 실시간 완료되면, 구매 관리자는 AI가 도출해 낸 파트너사별 최종 타결 단가와 절감된 사내 재무 지표 리포트를
|
|
대시보드에서 즉시 확인합니다.
|
|
</>,
|
|
<>
|
|
어떤 파트너사가 어느 카드(정산 단축, 물량 보증)를 수용하여 단가를 낮추었는지 시각적으로 분석되어 최저 계약 체결을
|
|
전면 검토할 수 있습니다.
|
|
</>,
|
|
],
|
|
checks: ["최종 단가 조율 결과 및 계약 체결 자동 확정", "총 누적 절감액(Savings) 및 마진 방어 통계 실시간 표기"],
|
|
mockup: "browser",
|
|
url: "https://console.negotium.ai/dashboard/reports",
|
|
gif: "/gifs/negotiation_result.gif",
|
|
gifAlt: "협상 결과 분석 대시보드 시연",
|
|
fallback: {
|
|
icon: Monitor,
|
|
iconWrapClassName: "w-14 h-14 bg-purple-50 rounded-2xl text-purple-600",
|
|
codeClassName: "bg-white text-purple-600",
|
|
title: "협상 결과 확인 시연 GIF 공간",
|
|
hint: "결과 보고 대시보드 기동 GIF를 배치하시면 이 영역에 자동으로 재생됩니다.",
|
|
},
|
|
},
|
|
]
|