From 03c22f9474f9b36cfff9bf20310a0fd99854942a Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Mon, 20 Jul 2026 09:09:09 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20landing:=20=EC=86=94=EB=A3=A8=EC=85=98?= =?UTF-8?q?=20=EB=9E=9C=EB=94=A9=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=20=E2=80=94=20=ED=9E=88=EC=96=B4=EB=A1=9C=C2=B7?= =?UTF-8?q?=EB=8D=B0=EB=AA=A8=C2=B7ROI=20=EC=8B=9C=EB=AE=AC=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=84=B9=EC=85=98,=20docker=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4(:3100)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 12 + landing/.gitignore | 3 + landing/Dockerfile | 15 + landing/app/app.css | 73 + landing/app/components/sections/contact.tsx | 213 ++ .../app/components/sections/core-values.tsx | 79 + landing/app/components/sections/final-cta.tsx | 59 + landing/app/components/sections/footer.tsx | 33 + landing/app/components/sections/header.tsx | 52 + .../components/sections/hero-glassmorphic.tsx | 85 + .../components/sections/hero-neumorphic.tsx | 83 + .../components/sections/how-it-works-demo.tsx | 268 ++ .../sections/negotiation-console.tsx | 279 ++ .../app/components/sections/reinforcement.tsx | 78 + .../app/components/sections/roi-simulator.tsx | 158 + landing/app/components/ui/button.tsx | 40 + landing/app/components/ui/device-mockups.tsx | 35 + landing/app/components/ui/input.tsx | 17 + landing/app/components/ui/logo.tsx | 8 + landing/app/components/ui/range-slider.tsx | 28 + landing/app/components/ui/section-heading.tsx | 32 + landing/app/components/ui/section.tsx | 49 + landing/app/components/ui/slate-renderer.tsx | 65 + landing/app/components/ui/typography.tsx | 58 + landing/app/lib/motion.ts | 15 + landing/app/lib/utils.ts | 6 + landing/app/root.tsx | 33 + landing/app/routes.ts | 3 + landing/app/routes/home.tsx | 82 + landing/app/types.ts | 20 + landing/package-lock.json | 3113 +++++++++++++++++ landing/package.json | 34 + landing/public/favicon.svg | 1 + landing/public/fonts/PretendardVariable.woff2 | Bin 0 -> 2057688 bytes landing/public/gifs/.gitkeep | 0 landing/public/gifs/negotiation.gif | Bin 0 -> 2222639 bytes landing/public/gifs/negotiation_result.gif | Bin 0 -> 1776269 bytes landing/public/gifs/quote_generation.gif | Bin 0 -> 4349041 bytes landing/public/logo.png | Bin 0 -> 58606 bytes landing/public/robots.txt | 2 + landing/react-router.config.ts | 7 + landing/tsconfig.json | 21 + landing/vite.config.ts | 13 + 43 files changed, 5172 insertions(+) create mode 100644 landing/.gitignore create mode 100644 landing/Dockerfile create mode 100644 landing/app/app.css create mode 100644 landing/app/components/sections/contact.tsx create mode 100644 landing/app/components/sections/core-values.tsx create mode 100644 landing/app/components/sections/final-cta.tsx create mode 100644 landing/app/components/sections/footer.tsx create mode 100644 landing/app/components/sections/header.tsx create mode 100644 landing/app/components/sections/hero-glassmorphic.tsx create mode 100644 landing/app/components/sections/hero-neumorphic.tsx create mode 100644 landing/app/components/sections/how-it-works-demo.tsx create mode 100644 landing/app/components/sections/negotiation-console.tsx create mode 100644 landing/app/components/sections/reinforcement.tsx create mode 100644 landing/app/components/sections/roi-simulator.tsx create mode 100644 landing/app/components/ui/button.tsx create mode 100644 landing/app/components/ui/device-mockups.tsx create mode 100644 landing/app/components/ui/input.tsx create mode 100644 landing/app/components/ui/logo.tsx create mode 100644 landing/app/components/ui/range-slider.tsx create mode 100644 landing/app/components/ui/section-heading.tsx create mode 100644 landing/app/components/ui/section.tsx create mode 100644 landing/app/components/ui/slate-renderer.tsx create mode 100644 landing/app/components/ui/typography.tsx create mode 100644 landing/app/lib/motion.ts create mode 100644 landing/app/lib/utils.ts create mode 100644 landing/app/root.tsx create mode 100644 landing/app/routes.ts create mode 100644 landing/app/routes/home.tsx create mode 100644 landing/app/types.ts create mode 100644 landing/package-lock.json create mode 100644 landing/package.json create mode 100644 landing/public/favicon.svg create mode 100644 landing/public/fonts/PretendardVariable.woff2 create mode 100644 landing/public/gifs/.gitkeep create mode 100644 landing/public/gifs/negotiation.gif create mode 100644 landing/public/gifs/negotiation_result.gif create mode 100644 landing/public/gifs/quote_generation.gif create mode 100644 landing/public/logo.png create mode 100644 landing/public/robots.txt create mode 100644 landing/react-router.config.ts create mode 100644 landing/tsconfig.json create mode 100644 landing/vite.config.ts diff --git a/docker-compose.yml b/docker-compose.yml index d3d5cb4..d429772 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ # negosium 서버: http://localhost:9300/docs # negosium 프론트: http://localhost:3300 # negodata 서버: http://localhost:9400/docs +# 솔루션 랜딩: http://localhost:3100 # agent 서버: http://localhost:9500/docs # anchoring 배치: 포트 없음 — 상주 스케줄러(격주 토 00:00 KST), docker logs anchoring 으로 확인 # @@ -60,6 +61,17 @@ services: - /app/node_modules restart: unless-stopped + # 솔루션 랜딩페이지 (react-router dev 서버. 배포는 `npm run build` 산출물 build/client 정적 서빙). + landing: + build: ./landing + container_name: negosium-landing + ports: + - "3100:3100" + volumes: + - ./landing:/app + - /app/node_modules + restart: unless-stopped + # 협상 에이전트 (negosium_db 공유, learning 스키마 사용). agent: build: ./agent diff --git a/landing/.gitignore b/landing/.gitignore new file mode 100644 index 0000000..27d1340 --- /dev/null +++ b/landing/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +.react-router diff --git a/landing/Dockerfile b/landing/Dockerfile new file mode 100644 index 0000000..37ade9d --- /dev/null +++ b/landing/Dockerfile @@ -0,0 +1,15 @@ +# front 와 동일 구성 — Debian slim(glibc) 로 alpine(musl)+esbuild 의 ETXTBSY 회피. +FROM node:22-slim + +WORKDIR /app + +# 의존성 먼저 설치 (레이어 캐시) +COPY package.json package-lock.json ./ +RUN npm install + +COPY . . + +EXPOSE 3100 + +# react-router dev — --host 0.0.0.0 로 컨테이너 외부 노출 (package.json scripts.dev) +CMD ["npm", "run", "dev"] diff --git a/landing/app/app.css b/landing/app/app.css new file mode 100644 index 0000000..e83ae98 --- /dev/null +++ b/landing/app/app.css @@ -0,0 +1,73 @@ +@import "tailwindcss"; + +/* Pretendard 가변폰트 self-host (public/fonts). + format 은 표준 'woff2' 고정 — 'woff2-variations' 는 윈도우 브라우저가 무시함. */ +@font-face { + font-family: 'Pretendard Variable'; + font-weight: 45 920; + font-style: normal; + font-display: swap; + src: url('/fonts/PretendardVariable.woff2') format('woff2'); +} + +/* + * 랜딩 디자인 토큰 (Toss 스타일 라이트 팔레트). + * 색은 전부 여기서만 정의하고, 컴포넌트는 토큰 클래스(text-ink, bg-primary …)만 쓴다. + */ +@theme { + --font-sans: 'Pretendard Variable', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + + /* 브랜드 */ + --color-primary: #3182F6; + --color-primary-deep: #1B64DA; /* hover */ + --color-primary-soft: #E8F3FF; /* 연한 파랑 배경 — 봇 말풍선, 배지 */ + + /* 텍스트. ink 는 다크 섹션에선 배경색으로도 쓴다 */ + --color-ink: #191F28; + --color-ink-soft: #4E5968; /* 보조 본문 */ + --color-ink-muted: #8B95A1; /* 캡션 */ + --color-ink-faint: #B0B8C1; /* 플레이스홀더·각주 */ + + /* 면·선 */ + --color-surface: #F9FAFB; /* 회색 섹션 배경 */ + --color-surface-neu: #F2F4F7; /* 뉴모피즘 히어로 전용 배경 */ + --color-fill: #F2F4F6; /* 회색 채움 — 탭 트랙, 보조 버튼 */ + --color-fill-hover: #EAECEF; + --color-line: #F2F4F6; /* 섹션 구분선 */ + --color-line-strong: #E5E8EB; /* 카드 테두리 */ + + --color-positive: #0B8F57; /* 낙찰·성공 강조 */ +} + +html { + scroll-behavior: smooth; + background-color: #ffffff; + color: var(--color-ink); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; +} + +/* 스크롤은 되지만 스크롤바는 숨김 (협상 콘솔 채팅 영역) */ +.scrollbar-none::-webkit-scrollbar { + display: none; +} +.scrollbar-none { + -ms-overflow-style: none; + scrollbar-width: none; +} + +/* 은은한 원형 글로우 장식 (히어로 배경) */ +.mesh-bg { + position: absolute; + width: 1000px; + height: 1000px; + border-radius: 50%; + background: radial-gradient(circle, rgba(49, 130, 246, 0.08) 0%, rgba(255, 255, 255, 0) 70%); + filter: blur(100px); + pointer-events: none; +} diff --git a/landing/app/components/sections/contact.tsx b/landing/app/components/sections/contact.tsx new file mode 100644 index 0000000..2fcc467 --- /dev/null +++ b/landing/app/components/sections/contact.tsx @@ -0,0 +1,213 @@ +import { useState } from "react" +import { AnimatePresence, motion } from "motion/react" +import { Building, CheckCircle2, Loader2, Mail, Phone, Send } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Input, Textarea } from "@/components/ui/input" +import { Section } from "@/components/ui/section" +import { SectionHeading } from "@/components/ui/section-heading" +import { Typography } from "@/components/ui/typography" +import { EASE_OUT_EXPO } from "@/lib/motion" +import type { LucideIcon } from "lucide-react" + +const EMPTY_FORM = { + companyName: '', + contactName: '', + email: '', + phone: '', + message: '', +} + +/** 도입 문의 폼. 백엔드 미연결 — 제출은 데모 처리(1.2초 후 성공 화면). */ +export function Contact() { + const [formData, setFormData] = useState(EMPTY_FORM) + const [status, setStatus] = useState<'idle' | 'submitting' | 'success'>('idle') + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!formData.companyName || !formData.contactName || !formData.email || !formData.phone) { + return + } + + setStatus('submitting') + setTimeout(() => setStatus('success'), 1200) + } + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setFormData((prev) => ({ ...prev, [name]: value })) + } + + const submitting = status === 'submitting' + + return ( +
+
+
+ + } + > + + +
+ + {status !== 'success' ? ( + +
+ + + + + + + +
+ +
+ + + + + + + +
+ + +