여기서 직접 체험 → 협상 예시 체험
데모 요청 → 실제 데모 받기
두 버튼은 성격이 전혀 다른데 문구가 그걸 구분하지 못했다.
1차는 페이지 안에서 바로 조작하는 인터랙티브 데모고, 2차는 이름·이메일을 받는
리드 폼이다. 클릭 직후 경험이 다르므로 문구도 갈라야 한다.
협상 예시 체험
고정 데이터 시뮬레이션이라 "예시"를 밝힌다. 다만 "보기"로 낮추지는 않았다 —
도착 섹션이 "직접 경험해 보세요" 이고 역할 카드가 "공급사로 해보기" 라,
버튼만 구경하는 말이면 클릭 전 기대와 도착 후 요구가 어긋난다.
실제 데모 받기
누르면 폼이 뜨고 데모는 이메일로 온다. "직접 체험" 이라 쓰면 눌렀을 때 바로
만질 줄 알았다가 입력을 요구받아 낚인 느낌이 된다. "받기" 는 이메일로 온다는
사실이 드러나서 폼이 뜨는 게 자연스럽다.
모달 제목도 "데모 요청" → "실제 데모 받기" 로 맞췄다. 버튼이 "받기" 인데 열린 창이
"요청" 이면 전환 순간에 다른 화면처럼 읽힌다.
제출 버튼은 "데모 신청" 그대로 둔다. 같은 원칙을 적용하면 그 클릭으로 실제 일어나는
일은 신청 접수이지 데모 수령이 아니다(수령은 이메일로 나중에).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
negotiation_annotated_3d.mp4(677K)를 NegotiationReplay 컴포넌트로 대체했다.
영상이라 두 가지를 못 고쳤고, 둘 다 픽셀에 구워진 것이 원인이었다.
화자
"아이마켓 구매 MD" 가 박혀 있었다. POC 고객사 이름이라 다른 MRO 회사에 영업할 때
쓸 수 없다. 이제 "구매 담당자" 로, 회사명 없이 역할만 쓴다 — 상수라 언제든 바꾼다.
색
실측하면 말풍선 #5A68CD, 목표가 라벨 #B96175, 와일드카드 배지 #DB840B 로 셋 다
토큰에 없는 색이었고, 정작 브랜드의 핵심 대비 장치인 민트(#0FFFD6, 상대편 색)는
한 번도 쓰이지 않았다. 이제 토큰만 쓴다 — 우리 쪽 파랑, 상대편 민트.
진행 막대도 파랑에서 민트로 차오르며 양쪽이 만나는 지점을 그린다.
부수 효과
- 다크 무대에 네이티브로 그리니 밝은 영상이 구멍처럼 뜨던 문제가 사라졌다.
카드로 감싸고 그림자로 띄우던 처리가 더는 필요 없다.
- 677K + 포스터 85K → 0. 어느 해상도에서도 선명하다(영상은 840x1260 고정이었다).
- role="img" + aria-label 로 스크린리더가 결론을 읽는다. 영상은 못 읽었다.
숫자는 지어내지 않고 DEMO_ITEMS[0] 을 그대로 쓴다. 위쪽 인터랙티브 데모와 같은
품목·같은 가격이라 둘을 다 본 사람에게 앞뒤가 맞는다.
구현 메모
- 화면 밖에서는 타이머를 멈춘다(IntersectionObserver). 페이지 최하단이라
대부분의 시간 동안 보이지 않는데 계속 도는 건 배터리를 쓰는 일이다.
- prefers-reduced-motion 이면 최종 상태를 정지 표시한다.
- 낙찰 카드 자리를 h-[74px] 로 항상 확보하고 내용만 켜고 끈다. 나타날 때 아래가
밀리면 페이지가 주기적으로 흔들린다. 대신 금액은 whitespace-nowrap — 고정 높이라
한 줄이라도 접히면 잘린다(실제로 "1,080,000" 뒤 "원" 이 접혔다).
검증: 1280x900 카드 319x74 · 375x812 285x74, 슬롯 초과·가로 넘침 없음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
① 이용 가이드 2번 탭 (협력사별 1:1 협상)
video 필드가 빠져 있어 이 탭만 1.7MB GIF(390x780)를 내려받고 있었다. 같은 내용의
mp4 가 저장소에 있는데 참조만 없었다 — 786K, 540x1080 으로 더 가볍고 더 크다.
GifSlot 이 video 를 우선 재생하므로 한 줄로 끝난다.
원본 캡쳐가 좌우로 잘려 문장이 중간에 끊기는 문제는 남아 있다(540x1080 원본 파일
자체에 잘린 채로 들어 있어 CSS 로 못 고친다). 재촬영이 필요하고 주석에 남겼다.
③ 최종 CTA — negotiation_annotated_3d.mp4
이용 가이드에는 넣지 않았다. 그 섹션은 "실제 화면으로 보여드립니다" 라고 약속하는데
이 영상은 재구성 목업이다(실제 UI 는 "아이마켓코리아 (구매 MD)", 영상은 "아이마켓 구매 MD").
1·3번 탭이 실제 캡쳐인데 2번만 목업이면 register 도 어긋난다.
전환을 요청하는 자리에는 그 약속이 없으므로 마케팅 컴포짓이 맞는 자리다.
- 영상 배경이 밝은 회색(#EEF4F5)이라 무대 위에 그냥 놓으면 구멍처럼 뜬다.
모서리를 깎고 그림자를 깔아 "떠 있는 물체"로 만들면 밝기 차이가 의도로 읽힌다.
- 폰 프레임이 영상에 내장돼 있어 PhoneFrame 으로 감싸지 않는다. 감싸면 이중 프레임이다.
- 2단이 되면서 width md(max-w-4xl) → lg(max-w-5xl), 제목은 lg 부터 한 단계 축소.
4xl 안에 세로 목업과 카피를 밀어넣으면 양쪽 다 좁아진다.
- preload="none" — 페이지 최하단이라 첫 로드에 677K 를 미리 받을 이유가 없다.
검증: 1280x900 2단 정상, 375x812 카피→영상 중앙 적층·가로 넘침 없음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
roi-simulator.tsx 는 어디에서도 import 되지 않아 화면에 나오지 않았다.
누락이 아니라 의도적으로 내린 것이다 — 9ccf42c("카피 정직화")에서 <ROISimulator /> 를
빼고 그 자리에 <Comparison />, <Faq /> 를 넣었다.
되살리지 않는 이유는 파일 자체에 있다. 카피는 "아직 실제 도입 실증 사례가 없는
신규 카테고리"라고 스스로 인정하면서 절감액을 제시하고, 계산은 근거 없는 추산이다
(협력사당 6.5시간). 브랜드 보이스 가드레일 통과 기준 3번 — 주장·수치에 출처가 붙어야
하고 출처 없는 주장은 FAIL — 과 정면으로 부딪힌다. 다시 붙이려면 근거부터 확보해야 한다.
range-slider.tsx 도 함께 지운다. 사용처가 roi-simulator 하나뿐이라 같이 죽는다.
되살릴 일이 생기면 git 이력에 그대로 남아 있다.
남은 고아 두 개는 이번 작업과 무관한 기존 항목이라 두었다:
components/ui/price-sparkline.tsx
components/ui/slate-renderer.tsx
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
구매 담당자 모드
공급사 모드와 같은 문제가 있었다. simulateBuyerRun 의 에이전트 첫 수가 marketLow 만
읊어서, 목표가를 얼마로 잡든 같은 문장이 나왔다 — 위임한 기준이 반영되는지 확인할
길이 없었다. 이제 목표가를 받았다고 명시하고, 시장 최저가 대비 위치에 따라 태도를
달리한다(근거부터 깔겠다 / 무리 없이 접근하겠다).
여는 말(협력사 정가 제시)은 아직 목표가를 모르는 시점이라 상수로 둔다.
무대 위 드래그 선택색
루트의 selection:text-primary 는 라이트 본문 기준이다. 그 값(#0101F3)을 무대(#070E24)
위에 얹으면 명암비 2.0:1 — app.css 의 --color-primary-on-stage 주석이 경고하는 바로 그
상황이다. 무대 섹션에서 텍스트를 드래그하면 글자가 사라졌다.
.stage-bg / [data-stage-hero] 안에서는 무대용 액센트로 갈아끼운다.
플레이 화면 유리 패널
컨트롤과 대화 로그가 섹션 배경 위에 그냥 얹혀 있어 무대와 같은 색이라 경계가 없었고,
조작 가능한 영역인지 읽히지 않았다. 재질은 무대 위 2차 CTA 와 같게 맞췄다 — 화면 안에
재질이 두 종류면 따로 논다. 테두리는 white/12 로 얕게: 면이지 조작 요소가 아니라
버튼만큼 세우면 카드가 버튼처럼 보인다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
공급사 모드에서 슬라이더를 움직여도 첫 응수의 문장과 숫자가 똑같았다.
원인은 두 겹이다.
- 근거 문장이 offer 를 참조하지 않았다. cardsUsed === 0 분기가 marketLow 만 읊었다.
- CONCESSION[0] = 0 이라 부르는 값(ask)도 앵커로 고정이다.
둘이 겹쳐서, 얼마를 제시하든 화면이 전혀 반응하지 않는 것처럼 보였다.
값 고수는 유지한다 — 앵커를 지키는 게 이 에이전트의 성격이고, 제품이 파는 신뢰가
거기서 나온다. 대신 무엇에 대한 응수인지는 매번 달라지게 했다.
정가 이상 정가 그대로는 검토가 어렵습니다 + 시장 최저가 제시
최저가 위 제시가가 최저가보다 얼마 높은지 구체 수치로
최저가 이하 최저가 선까지 내려온 것을 인정 + 기준까지 남은 격차
입력란 숫자에 콤마 누락도 같이 고쳤다. 화면의 다른 금액은 전부 won() 인데 여기만
1018800 으로 나와 같은 값으로 안 읽혔다. 확정 시 콤마를 넣고 파싱할 때 벗긴다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
탁함: 스크림이 원인이었다. stage-deep(#03060F) 을 90/60/95 알파로 전면에 깔아
가장 옅은 지점에서도 영상이 40% 만 남았다. 그 값은 헤드라인 둘째 줄이 회색(/60)이던
시절 그 글자를 살리려고 넣은 것인데, 헤드라인이 흰색이 된 뒤로는 근거가 없어졌다.
게다가 중앙이 가장 옅어서 — 정작 글자가 있는 자리 — 보호 목적으로도 방향이 틀렸다.
흐림: 1280x720 crf 30 은 전면 배경으로 무르다. 1600x900 crf 24 로 재인코딩.
업스케일 1.19배 → 0.96배(다운스케일)로 바뀌어 선명도가 올라간다. 631K → 1.7M.
스크림을 걷었더니 이번엔 리드 문단(17px)이 밝은 입자 위에서 안 읽혔다. 헤드라인은
흰색 74px 이라 버티지만 본문은 못 버틴다. 전면을 덮으면 영상이 탁하고, 다 걷으면
본문이 안 읽힌다 — 둘 다 만족하려면 덮는 범위를 글자가 있는 자리로 좁혀야 한다.
세로 그라데이션 50 / 투명 / 45 헤더 nav 와 하단 전환만 보호
중앙 방사형 85 → 55 → 투명 카피 뒤만 국소적으로
색은 color-mix 로 --color-stage-deep 를 참조한다. 하드코딩하면 토큰이 둘이 된다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
데모 — 추천하고, 사용자가 고친다
슬라이더 하나뿐이라 정확한 값을 넣을 수 없었고, 에이전트가 추천하는 자리도 없었다.
"에이전트가 제안하면 사람이 확정한다"가 제품의 관계인데 UI 가 그걸 보여주지 못했다.
- 에이전트 추천가 + 근거 + 원클릭 적용. 값은 지어내지 않고 시뮬레이터가 실제로 쓰는
수치에서 가져온다 — 구매자는 anchor(reachable = target >= anchor 인 최저선),
공급사는 marketLow(에이전트가 응수할 때 인용하는 값). 지어낸 숫자를 추천이라 부르면
사용자가 그대로 넣었을 때 결과가 설명되지 않는다.
- 직접 입력란 추가. 실제 구매 담당자는 "얼마까지"라는 숫자를 이미 들고 온다.
타이핑 중에는 clamp 하지 않고 blur/Enter 에서 확정한다 — 입력 즉시 범위로 당기면
"1" 을 치는 순간 튕겨서 손가락과 싸운다.
- 슬라이더·직접입력·추천적용이 commitOffer 한 곳을 거친다. 경로가 셋이면 값이 셋 된다.
- 에이전트 응수 후 offer 만 갱신하고 입력란은 두던 버그 수정. 슬라이더와 숫자가 어긋났다.
- 잠금 조건(role === buyer && turns.length > 0)을 inputLocked 로 묶었다.
히어로 헤드라인
"동시에 협상합니다." 를 흰색으로. 예전 캔버스는 하단 밴드에만 입자가 있어 헤드라인
뒤가 거의 단색이었고 그때는 투명도 60% 도 읽혔다. 배경이 영상으로 바뀌며 밝은 요소가
글자 뒤로 지나가 대비가 무너졌다 — 배경이 움직이는 위에서는 가독성이 조판 장치보다
우선한다. 2톤은 배경이 정적인 최종 CTA 에 남아 있다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
랜딩은 ssr:false 정적 빌드라 서버 라우트가 없다. Vercel /api 디렉터리 함수로 받는다.
클라이언트가 같은 오리진(/api/lead)으로 쏘므로 엔드포인트 환경변수도 CORS 도 없다.
api/lead.ts — 리드를 잃지 않는 것이 유일한 책임
1) 먼저 구조화 로그를 남긴다. 웹훅이 죽어도 Vercel 로그에 리드가 남는다.
2) LEAD_WEBHOOK_URL 이 있으면 전달(Slack Incoming Webhook·Zapier·자체 API 모두 POST).
Slack 용 text 와 원본 필드를 함께 실어 수신처를 바꿔도 이 파일을 안 고친다.
3) 웹훅 실패해도 200 — 1) 에서 리드를 확보했으니 방문자에게 재입력을 시킬 이유가 없다.
대신 실패는 로그에 크게 남긴다.
봇 함정(website) 값이 차 있으면 조용히 200. 400 을 주면 어떤 필드가 함정인지 알려주는 셈이다.
contact.tsx — 가짜 성공 제거
기존엔 setTimeout 1.2초 뒤 성공 화면만 띄우고 아무 데도 보내지 않았다. 화면은
"접수되었습니다"인데 영업이 받을 리드는 없었다. 이제 같은 함수로 보내고 서버가
리드를 확보했을 때만 성공을 띄운다. 실패 시 role="alert" 로 안내.
vercel.json — SPA 리라이트가 /api 를 삼키지 않도록 제외.
"/(.*)" 그대로 두면 /api/lead 요청이 index.html 로 갔다.
데모 요청 모달 카피
"이름과 이메일만 남겨주시면, 담당자가 일정을 잡아 연락드립니다"
→ "성함과 이메일만 남겨주시면, 사용해보실 수 있는 Demo를 보내드립니다!"
성공 화면도 같은 약속으로 맞췄다. 폼은 데모를 보낸다는데 성공 화면이 일정을
잡는다고 하면 방문자가 무엇을 기다려야 하는지 모른다. 라벨도 성함으로 통일.
⚠️ LEAD_WEBHOOK_URL 미설정 시 리드는 Vercel 로그에만 남는다. 로그는 최후의 그물이지
CRM 이 아니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
카피
- 데모 섹션 헤드라인: "직접 협상해 보세요 / 에이전트는 정해진 기준을 벗어나지 않습니다"
→ "사용할수록 진화하는 에이전틱 협상, / 직접 경험해 보세요!"
예전 카피는 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>
배경이 설명을 지던 구조를 걷어냈다. 예전 캔버스는 negotium 파이프라인을 직접
그렸지만(입자 240개·6단계 서사·무리 라벨), 이제 배경은 배경으로만 쓴다.
설명은 카피와 아래 섹션이 맡는다.
영상: hero_veo31 0~5초를 크로스페이드 루프로. 그냥 자르면 끝에서 처음으로 튀어서
끝 0.8초를 앞 0.8초와 겹쳐 이음새를 없앴다(결과 4.25초, 첫↔끝 프레임 RMS 18.6/255).
1280x720 h264 631K + 포스터 58K. webm 은 뺐다 — VP9 이 1.0M 으로 h264 보다 커서
둘을 다 실을 이유가 없다.
- CLUSTER_ANCHORS 라벨 제거. 좌표 소스가 사라진 라벨은 화면비마다 어긋나기만 하고
아무것도 설명하지 못한다.
- DataFlowPhase 를 orbit-ring 으로 이관 후 data-flow-canvas.tsx(500줄) 삭제.
단계 이름을 실제로 쓰는 건 강화학습 섹션의 순환 링뿐이다.
- 영상은 가로 화면에서만 튼다. 16:9 를 세로에 object-cover 로 깔면 가로가 62% 잘리고
남은 38% 가 1.7배로 확대된다(848x1220 실측) — 입자가 거대한 보케로 뭉개져 배경이
아니라 노이즈가 된다. 세로에서는 무대 그라데이션만 남긴다. 모바일 대역폭도 아낀다.
- prefers-reduced-motion 도 같은 경로로 영상을 감춘다. 포스터가 뒤를 받친다.
- 스크림은 색 하나(stage-deep)로 알파만 움직인다. 중간 stop 에서 색을 바꿨더니
그 지점이 가로선으로 읽혔다.
- 밴드 개념이 사라져 히어로 레이아웃을 카피 중앙 정렬로 되돌렸다. 직전 커밋의
--band-top 기준 높이 계산은 더 이상 필요 없다.
검증: 1440x860 크롭 7%, 375x812 영상 숨김·제목 헤더 여유 152px, 콘솔 에러 없음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
버튼 행이 입자 밴드를 파고들었다. 1280x720 에서 34px, 무리 라벨과는 7px.
1440x900 에서만 여유가 있어(45px) 안 보이던 문제다.
원인은 두 값이 서로 다른 기준으로 움직인 것:
카피 상단 여백 19vh 화면 비율
카피 높이 351px 고정
밴드 시작 0.63×높이 화면 비율
카피만 px 고정이라 화면이 짧아지면 밴드가 카피를 덮는다.
0.19H + 351 > 0.63H → H < 798px 이면 전부 겹친다.
1280x720, 1366x768 같은 흔한 노트북 높이가 여기 들어간다.
pt-[19vh] 는 위치만 잡을 뿐 경계를 강제하지 못한다. 그래서:
- 카피 영역 높이를 밴드 시작선에 묶었다. 뷰포트와 무관하게 구조적으로 못 겹친다.
- BAND_TOP 을 export 해 캔버스와 카피가 같은 값을 쓴다. 라벨 좌표가 이미 쓰던 방식이다.
- 높이·밴드를 한 식에서 뽑는다. 따로 두면 어긋난다 — 밴드를 63vh 로 두고 높이만
키웠더니 짧은 화면에서 변수가 실제 밴드보다 위를 가리켰다.
- min-h 바닥 780px. 밴드 위 공간에 헤더(104)+카피(351)+여유(24)=479px 가 들어가려면
높이 760px 이 하한이라, 정렬만으로는 짧은 화면을 못 구한다. 살짝 스크롤될 뿐이다.
- pt-[104px] 를 md 이상에서도 유지. 빼면 가운데 정렬이 카피를 fixed 헤더 밑으로 민다.
검증(실측, 음수 = 밴드 밖):
1280x620 +20 → -30, 헤더 여유 +12, 라벨 75px
1280x720 +34 → -30, 헤더 여유 +12, 라벨 75px
1440x900 -45 → -68, 헤더 여유 +50, 라벨 122px
375x812 -57, 헤더 여유 +6
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
디자인 시스템이 색·활자만 소유하고 간격은 각 섹션 파일에 흩어져 있어,
같은 관계에 값이 여러 개 생겼다. 스크롤할 때 섹션마다 호흡이 달라지던 원인.
eyebrow → title 12/20px → 16px
title → description 16/20/32px → 20px (display 티어 32px)
머리 → 본문 48~96px 5종 → 56px / 80px(md)
컨트롤 → 본문 56/64px → 48px
리드 → CTA 56/48px → 48px
- SectionHeading 이 리듬을 소유하고 HEADING_GAP·CONTROL_GAP·CTA_GAP·
DISPLAY_LEAD_GAP 를 노출. 섹션 파일에서 mb-24 같은 값을 직접 쓰지 않는다.
- negotiation-demo·final-cta 가 Section/SectionHeading 을 우회해 머리를 직접
조판하고 있었다 — 아이브로우 간격이 이 둘만 20px 이던 원인. 프리미티브로 환원.
- SectionHeading 이 eyebrow 없이도 빈 span 을 렌더해 유령 여백이 생기던 버그 수정.
- descriptionClassName 의 text-[17px] 중복 선언 제거. lead 변형이 이미 갖고 있는
모바일 축소 단계(text-[16px] sm:text-[17px])를 지우고 있었다.
- reinforcement: text-balance 가 쉼표를 넘어 끊던 헤드라인을 <br /> 로 명시.
reinforcement 만 gap="none" 인데, 머리가 그리드 셀 안이라 마진이 상쇄되지 않아
items-center 정렬을 밀기 때문. 간격은 그리드 래퍼가 진다.
검증: tsc --noEmit 통과, 프로덕션 빌드 통과. DOM 실측으로 375/868/1440px
전 구간에서 머리→본문·eyebrow→title·컨트롤→본문 값 일치 확인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
답답함의 원인은 가로 비율이 아니라 세로 간격이었다. 헤드라인 80px 에 대해
헤드라인↔서브 28px, 서브↔CTA 40px 는 너무 촘촘했다.
- 간격 확대: mt-7 → mt-11(44px), mt-10 → mt-14(56px).
- 헤드라인 80 → 74px. 글자 실폭이 뷰포트의 40% (레퍼런스 statworx 는 48%).
- 측정 주의사항을 주석에 남김: h1 은 블록이라 getBoundingClientRect 로 재면
컨테이너 폭(848px)이 나온다. Range 로 텍스트 노드를 재야 실제 글자 폭이다.
이걸 헷갈려 한 번 과교정(66px, 36%)했다가 되돌렸다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
히어로에 헤드라인·서브·CTA 2개·군집 밴드·순환 링이 다 들어가 세로 예산이 포화라
위를 벌리면 아래가 터지는 상태였다. 링을 옮겨 자리를 만든다.
- 순환 링을 강화학습 섹션으로. "협상할수록 좋아진다"를 말하는 자리라
루프가 의미상 속하는 곳이기도 하다. 텍스트 좌 / 링 우 2단 배치.
- OrbitRing 에 자체 시계 모드 추가 — progressRef·phase 를 안 주면 스스로 돈다.
덕분에 히어로 캔버스와 무관한 섹션에서도 단독으로 쓸 수 있다.
- 히어로 상단 여백 14vh → 19vh, 밴드를 하단 전체(0.63/0.30)로 확장.
- 군집 라벨 y 계산 버그 수정. 밴드 지역 좌표(0.5)를 화면 좌표로 착각해
밴드를 옮길 때마다 라벨이 엉뚱한 곳으로 갔다. 이제 행 수·행간에서
격자 상단을 실제로 계산해 투영한다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 글리프 크기를 화면 폭에서 파생. 격자 간격은 dx × width 로 폭에 비례하는데
글리프가 CSS px 고정이라 375px 에서 셀(7.7px)보다 커져(22px) 서로 겹쳐 덩어리가 됐다.
- 좁은 화면(<640px)에서는 밴드를 링 뒤로 내리고 군집 라벨을 접는다.
모바일은 CTA 가 세로로 쌓여 세로 예산이 부족하고, 375px 에서 3군집을 나란히 두면
군집당 100px 남짓이라 어차피 판독이 안 된다. 의미는 순환 링이 담는다.
- 히어로 상단 여백을 vh 대신 모바일 고정값으로. 헤더는 고정 높이라
pt-[7vh](모바일 57px)로는 헤더(약 70px)를 피할 수 없어 헤드라인이 겹쳤다.
- 데스크톱 상단 여백 복원(14vh)과 밴드 재배치(0.575/0.155), 링 290px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 군집 좌표를 투영 후 위치에서 역산하도록 변경.
sx = 0.5 + (x−0.5)·k 라 깊이마다 당겨지는 양이 다르고, 가장 깊은 협력사가
중앙으로 제일 많이 끌려와 구도가 왼쪽으로 쏠렸다. 이제 화면 19/50/81% 에
대칭으로 놓인다 — 라벨도 같은 값을 써서 어긋나지 않는다.
- 순환 링을 원근 타원에서 정원으로 교체(orbit-ring). 점선 궤도만 회전하고
마디·라벨은 고정이다. 눕힌 타원은 깊이는 생기지만 "닫힌 루프"의 완결감이 없다.
- 학습 단계 입자 궤도 제거. 화면을 가로지르는 큰 선이 순환 링과 의미가 겹치고
시선을 뺏었다. 순환은 링이 맡고 입자는 안쪽으로 흡수만 한다.
- Playfair Display self-host(latin 서브셋 39KB). 구글 폰트 CDN 제거 —
서드파티 요청이 LCP 를 흔들고 방문자 IP 가 외부로 나간다.
- 히어로 CTA "협상 시연 보기" → "직접 협상해 보기".
데모는 실제로 동작한다(앵커 이하 낙찰 · 카드 소모 역제안 · 소진 시 개찰 확인).
수동태 문구는 인터랙티브 기능을 영상처럼 보이게 해 전환을 깎는다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
레퍼런스(statworx·jitter·infinith) 실측값으로 디자인 시스템을 다시 잡고,
스크롤 재생형 콘솔을 사용자가 직접 협상하는 데모로 교체했다.
디자인 토큰
- 타이포: 굵기·크기·자간을 함께 조정(섹션 제목 44→64px/600→800/자간 -0.042em).
한글 행간은 1.07 — 라틴 기준 0.95 를 쓰면 네모틀이 꽉 차 줄이 닿는다.
- 모서리: 12가지로 흩어져 있던 값을 card 8px / control 4px / full 셋으로 수렴.
20~32px 짜리 뭉툭한 카드가 제네릭 SaaS 의 가장 강한 신호였다.
- 다크 무대 토큰 신설(stage/on-stage/counter) + 배경 그라데이션.
- primary-on-stage(#7C8CFF) 추가 — #0101F3 은 무대 배경 대비 2.0:1 로
WCAG 최소(4.5:1)의 절반도 안 돼 다크 위에서 읽히지 않았다.
- 영문 디스플레이 서체 Playfair Display self-host(latin 서브셋 39KB).
히어로
- 배경 캔버스가 실제 파이프라인을 그린다: 수집→분류→대조→산정→협상→학습.
분류 단계는 거래 품목(시트)·사용자·협력사로 갈라지고 라벨이 붙는다.
- z 좌표 + 원근 투영. 군집마다 z 평면을 하나씩 줘 격자는 온전한 채 깊이만 생긴다.
- 순환 링(정원, 점선 궤도 회전). 진행도는 ref 로 공유해 60fps 리렌더를 피한다.
- 카피에 진입 애니메이션을 걸지 않는다 — LCP 요소라 opacity:0 으로 시작하면
하이드레이션 전까지 첫 화면이 빈다.
협상 데모 (신규)
- 역할 선택(공급사/구매 담당자) → 품목·단가 제시 → 턴 협상 → 낙찰/개찰 → 상담 신청.
- 판정 규칙은 실제 엔진과 동일: 앵커링가 이하 낙찰, 협상 카드 3장, 소진 시 개찰.
- 로직은 lib/negotiation-sim.ts 순수 함수로 분리 — 나중에 agent 서비스 연동 시
컴포넌트를 건드리지 않는다.
카피 감사
- "봇" 21곳 → "에이전트"(로봇 아이콘 3개 포함). 대리인은 조달 실무의 정당한 역할이다.
- "악역은 봇이", "단가를 깎는 악역" 제거 — 협력사를 적으로 규정하는 프레임은
같은 페이지 FAQ(공급사 만족도 82%)와 모순이고, 동반성장 평가를 받는
대기업 구매팀 결재 라인에 올릴 수 없다.
- 번역투 최상급(초정밀·압도적·완벽히·최적의)과 데모 대화문 전면 재작성.
버그 수정
- 헤더가 흰 배경 위에 흰 글자 버튼을 그리던 문제. onStage 를 스크롤 수치로 계산해
하이드레이션 전 스크롤 복원 시 초기값이 굳었다 → IntersectionObserver 로 교체.
- 버튼 transition-all → transition-colors (variant 교체 시 배경 플리커).
- 폼 입력 14px → 16px. iOS 사파리는 16px 미만 입력에 포커스하면 화면을 확대해
모바일 폼 이탈을 만든다.
정리
- 대체된 히어로 3종·기존 협상 콘솔 삭제(약 760줄).
- 라벨 아이콘·알약 배지·blur(120px) 글로우 블롭 제거.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 채팅 링크로 바로 진입하면 세션이 협상생성(1)에 머물러 오프닝 메시지가 seed 되지 않던 문제:
chat init/messages 에서 participate 와 동일하게 협상중(2)으로 전이(마감 견적·마감시간 초과는 제외)
- 부가정보 저장이 1301 로 실패하던 문제: '협상완료' 요약은 chat_end=false 라 세션이 아직 협상중이므로,
마지막 말풍선이 summaryRSP/CM 이면 협상중 세션도 저장 허용
- 채팅 언마운트 시 messages 캐시도 제거 — staleTime:Infinity 라 빈 목록이 남아 재진입해도 빈 화면이던 문제
- 채팅 스크롤을 컨테이너 기준 scrollTo 로 변경(rAF 후 실행) — 요약·폼이 뒤늦게 레이아웃되면 중간에 멈추던 문제
- 모바일 채팅 상단 여백 제거(pt-[64px] 는 우측 협상절차 카드가 있는 lg 이상만)
- 노치 대응: viewport-fit=cover + safe-t/b/x 유틸 신설, 헤더·하단 액션 덱·드로어·랜딩 헤더에 적용, h-screen→100dvh