카피
- 데모 섹션 헤드라인: "직접 협상해 보세요 / 에이전트는 정해진 기준을 벗어나지 않습니다"
→ "사용할수록 진화하는 에이전틱 협상, / 직접 경험해 보세요!"
예전 카피는 FAQ 2번("에이전트가 우리 기준을 벗어나 합의해 버리면요?")을 헤드라인으로
끌어올린 것이었다. 아직 생기지도 않은 반론에 가장 비싼 한 줄을 쓰고 있었고,
부정문이라 "안 하는 일"을 약속했다 — 체험을 권하는 자리에 맞지 않는다.
방어는 FAQ 에 그대로 남아 있다.
- 히어로 1차 CTA: "직접 협상해 보기" → "여기서 직접 체험"
- 히어로 2차 CTA: "도입 문의" → "데모 요청". 기존엔 #contact-section 으로 가서
GNB "상담 신청" 과 목적지가 같았다 — 버튼만 둘이고 경로는 하나였다.
리드 캡처
- DemoRequestModal 신규. 이름·이메일 두 칸만 받는다. 필드가 늘수록 이탈이 늘고,
영업 첫 연락에 필요한 최소가 그 둘이다. 나머지는 통화에서 채운다.
- 모달로 띄우는 이유: 히어로에서 관심이 생긴 자리에서 받아야 한다. 스크롤로
내려보내면 그 사이에 식는다. 고관여 폼(상담 신청)은 그대로 두고 그 앞단을 만든 것.
- 접근성: Esc 닫기, Tab 을 모달 안에 가둠, 열릴 때 첫 필드 포커스·닫힐 때 복원,
aria-modal/labelledby, 배경 스크롤 잠금, prefers-reduced-motion 대응.
전송 경로 — 성공을 위조하지 않는다
contact.tsx 는 1.2초 뒤 성공 화면만 띄우고 아무 데도 보내지 않는다("백엔드 미연결").
리드 제너레이션에서 그 방식은 최악이다. 화면은 접수됐다고 하는데 영업이 받을 리드는
없고, 아무도 그 사실을 모른다. 그래서 lib/lead.ts 는 VITE_LEAD_ENDPOINT 가 없으면
not-configured 를 돌려주고, 모달은 그걸 실패로 처리해 상담 신청 경로를 안내한다.
⚠️ VITE_LEAD_ENDPOINT 를 설정하기 전까지 데모 요청은 저장되지 않는다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
5.6 KiB
TypeScript
102 lines
5.6 KiB
TypeScript
import { useState } from "react"
|
|
import { ArrowRight } from "lucide-react"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import { DemoRequestModal } from "@/components/ui/demo-request-modal"
|
|
import { CTA_GAP, DISPLAY_LEAD_GAP } from "@/components/ui/section-heading"
|
|
import { Typography } from "@/components/ui/typography"
|
|
|
|
/**
|
|
* 다크 무대 히어로 — 배경 루프 영상.
|
|
*
|
|
* 예전에는 캔버스가 negotium 파이프라인을 직접 그렸고(입자 240개·6단계 서사·무리 라벨),
|
|
* 배경이 곧 설명이었다. 지금은 배경을 배경으로만 쓴다 — 설명은 카피와 아래 섹션이 맡는다.
|
|
* 그래서 무리 라벨(CLUSTER_ANCHORS)도 함께 걷어냈다. 좌표 소스가 사라진 라벨은
|
|
* 화면비가 바뀔 때마다 어긋나기만 하고 아무것도 설명하지 못한다.
|
|
*
|
|
* 배경이 의미를 지지 않으므로 카피 가독성이 1순위다. 그래서:
|
|
* - 영상 위에 스크림을 깐다. 예전 캔버스는 하단 밴드에만 입자가 있어 상단이 비었지만,
|
|
* 이 영상은 화면 전체에 밝은 요소가 흩어져 흰 글자가 묻힌다.
|
|
* - 카피는 화면 중앙 정렬. 밴드 개념이 없으니 상하로 나눌 이유도 없다.
|
|
*/
|
|
export function HeroDataFlow() {
|
|
const [demoOpen, setDemoOpen] = useState(false)
|
|
|
|
return (
|
|
<section
|
|
data-stage-hero
|
|
className="stage-bg relative min-h-screen text-on-stage flex flex-col overflow-hidden"
|
|
>
|
|
{/* 영상은 가로 화면에서만 튼다.
|
|
16:9 소스를 세로 화면에 object-cover 로 깔면 가로가 62% 잘리고 남은 38% 가
|
|
1.7배로 확대된다(848x1220 실측) — 데이터 입자가 거대한 보케로 뭉개져서
|
|
배경이 아니라 노이즈가 된다. 캔버스는 뷰포트에 맞춰 그렸으니 없던 문제다.
|
|
세로에서는 무대 그라데이션(stage-bg)만 남긴다. 그게 브랜드 배경이라 빈 화면이 아니다.
|
|
prefers-reduced-motion 에서도 같은 이유로 영상을 감춘다 — 포스터가 뒤를 받친다. */}
|
|
<div
|
|
aria-hidden
|
|
className="absolute inset-0 hidden landscape:block motion-reduce:hidden bg-cover bg-center"
|
|
style={{ backgroundImage: "url(/gifs/hero_loop.jpg)" }}
|
|
>
|
|
<video
|
|
className="w-full h-full object-cover"
|
|
src="/gifs/hero_loop.mp4"
|
|
poster="/gifs/hero_loop.jpg"
|
|
autoPlay
|
|
muted
|
|
loop
|
|
playsInline
|
|
preload="metadata"
|
|
/>
|
|
</div>
|
|
|
|
{/* 카피 가독성용 스크림. 색은 하나(stage-deep)로 두고 알파만 움직인다 —
|
|
중간 stop 에서 색이 바뀌면 그 지점이 가로선으로 읽힌다(stage/70 을 끼웠을 때 발생). */}
|
|
<div
|
|
aria-hidden
|
|
className="absolute inset-0 pointer-events-none bg-linear-to-b from-stage-deep/90 via-stage-deep/60 to-stage-deep/95"
|
|
/>
|
|
|
|
{/* 아래 라이트 섹션과의 경계를 부드럽게 */}
|
|
<div aria-hidden className="absolute inset-x-0 bottom-0 h-32 bg-linear-to-b from-transparent to-stage pointer-events-none" />
|
|
|
|
{/* pt-[104px] 는 fixed 헤더 회피용 바닥. 그 아래 남은 공간에서 수직 중앙 정렬한다. */}
|
|
<div className="relative z-10 w-full flex-1 pt-[104px] pb-16 flex items-center">
|
|
<div className="max-w-4xl mx-auto px-6 w-full text-center">
|
|
{/* 히어로 카피에는 진입 애니메이션을 걸지 않는다.
|
|
여기가 LCP 요소라 opacity:0 으로 시작하면 하이드레이션 전까지 첫 화면이 비고,
|
|
광고 유입처럼 JS 가 느린 환경에서 그대로 손실이 된다. 모션은 배경 영상이 맡는다. */}
|
|
{/* 2톤 헤드라인의 약한 쪽은 별도 회색이 아니라 같은 흰색의 투명도로 낮춘다.
|
|
네이비 위에 라벤더 회색을 얹으면 화면에 색상각이 하나 더 생겨 탁해진다. */}
|
|
<Typography variant="stageDisplay">
|
|
<span className="text-white">협력사 전원과 1:1로,</span>
|
|
<br />
|
|
<span className="text-on-stage-soft/60">동시에 협상합니다.</span>
|
|
</Typography>
|
|
|
|
{/* 히어로는 h1 이어야 해서 SectionHeading(h2 고정)을 못 쓴다. 대신 간격만은
|
|
같은 상수를 물려 최종 CTA 와 리듬이 어긋나지 않게 한다. */}
|
|
<p className={`${DISPLAY_LEAD_GAP} text-base sm:text-lg text-on-stage-soft leading-relaxed break-keep max-w-2xl mx-auto`}>
|
|
품목·목표가·마감일만 정하면 됩니다. 협상 에이전트가 협력사마다 따로 붙어 단가를 조율하고, 마감 시각에
|
|
최저 투찰가로 낙찰까지 판정합니다.
|
|
</p>
|
|
|
|
{/* 2차 CTA 는 상담 신청(#contact-section)과 목적지가 겹쳐 사실상 같은 버튼이었다.
|
|
데모 요청으로 갈라서, 고관여 폼(상담 신청) 앞단에 저관여 리드 캡처를 둔다. */}
|
|
<div className={`${CTA_GAP} flex flex-col sm:flex-row gap-3 justify-center`}>
|
|
<Button href="#how-it-works" variant="stage" size="stageRound" className="group">
|
|
<span>여기서 직접 체험</span>
|
|
<ArrowRight className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
|
|
</Button>
|
|
<Button type="button" variant="stageGhost" size="stageRound" onClick={() => setDemoOpen(true)}>
|
|
데모 요청
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DemoRequestModal open={demoOpen} onClose={() => setDemoOpen(false)} />
|
|
</section>
|
|
)
|
|
}
|