From 87b896fc2fb28df01103151278deed11f8e1c596 Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Fri, 31 Jul 2026 17:16:28 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20landing:=20=ED=9E=88=EC=96=B4=EB=A1=9C?= =?UTF-8?q?=20=EA=B5=AC=EB=8F=84=20=EC=A2=8C=EC=B8=A1=20=EC=8F=A0=EB=A6=BC?= =?UTF-8?q?=20=C2=B7=20=EC=88=9C=ED=99=98=20=EB=A7=81=20=EC=A0=95=EC=9B=90?= =?UTF-8?q?=ED=99=94=20=C2=B7=20Playfair=20self-host?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 군집 좌표를 투영 후 위치에서 역산하도록 변경. 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 --- .../app/components/sections/hero-dataflow.tsx | 4 ++-- .../app/components/ui/data-flow-canvas.tsx | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/landing/app/components/sections/hero-dataflow.tsx b/landing/app/components/sections/hero-dataflow.tsx index 845e5c4..b7084f2 100644 --- a/landing/app/components/sections/hero-dataflow.tsx +++ b/landing/app/components/sections/hero-dataflow.tsx @@ -36,7 +36,7 @@ export function HeroDataFlow() { className={`absolute -translate-x-1/2 -translate-y-1/2 whitespace-nowrap text-[13px] font-semibold text-on-stage-soft/80 transition-opacity duration-500 ${ phase === "cluster" ? "opacity-100" : "opacity-0" }`} - style={{ left: `${anchor.x * 100}%`, top: `${anchor.labelY * 100}%` }} + style={{ left: `${anchor.screenX * 100}%`, top: `${anchor.labelY * 100}%` }} > {anchor.label} @@ -44,7 +44,7 @@ export function HeroDataFlow() { {/* 카피는 상단 55%, 데이터 흐름은 하단 밴드. 둘이 겹치지 않게 층을 나눈다. */} -
+
{/* 히어로 카피에는 진입 애니메이션을 걸지 않는다. 여기가 LCP 요소라 opacity:0 으로 시작하면 하이드레이션 전까지 첫 화면이 비고, 광고 유입처럼 JS 가 느린 환경에서 그대로 손실이 된다. 모션은 배경 캔버스가 맡는다. */} diff --git a/landing/app/components/ui/data-flow-canvas.tsx b/landing/app/components/ui/data-flow-canvas.tsx index 3dbdeda..843bd0b 100644 --- a/landing/app/components/ui/data-flow-canvas.tsx +++ b/landing/app/components/ui/data-flow-canvas.tsx @@ -70,13 +70,25 @@ export const perspective = (z: number) => 0.42 + 0.58 * z /* 분류 단계에서 각 무리가 앉는 자리와 깊이. 무리마다 z 평면을 하나씩 준다 — 입자마다 z 를 따로 주면 격자가 3D 로 뒤틀려 - 표로 안 읽힌다. 무리 단위로 주면 격자는 온전한 채 그룹 사이에만 깊이가 생긴다. */ -export const CLUSTER_ANCHORS = [ - { key: "item", x: 0.17, z: 0.9, labelY: 0.525, label: "거래 품목" }, - { key: "user", x: 0.45, z: 0.66, labelY: 0.525, label: "사용자" }, - { key: "partner", x: 0.76, z: 0.44, labelY: 0.525, label: "협력사" }, + 표로 안 읽힌다. 무리 단위로 주면 격자는 온전한 채 그룹 사이에만 깊이가 생긴다. + + x 는 "투영된 뒤 어디에 놓일지"를 먼저 정하고 역산한 값이다. + 투영은 깊이마다 다른 배율로 중앙으로 당기기 때문에(k = 0.42+0.58z), 세계 좌표를 + 눈대중으로 고르면 가장 깊은 무리가 제일 많이 끌려와 구도가 한쪽으로 쏠린다. */ +const CLUSTER_PLAN = [ + { key: "item", screenX: 0.19, z: 0.9, label: "거래 품목" }, + { key: "user", screenX: 0.5, z: 0.66, label: "사용자" }, + { key: "partner", screenX: 0.81, z: 0.44, label: "협력사" }, ] as const +export const CLUSTER_ANCHORS = CLUSTER_PLAN.map((c) => ({ + ...c, + /** 투영 전 세계 좌표 — 투영을 거치면 screenX 에 놓인다 */ + x: VANISH_X + (c.screenX - VANISH_X) / perspective(c.z), + /** 라벨은 격자 위. 투영 후 y 라서 화면 좌표 그대로 쓴다 */ + labelY: VANISH_Y + (0.5 - VANISH_Y) * perspective(c.z), +})) + /* 격자 배치 — 종류별 [열, 열간격(x, 화면비), 행간격(y, 밴드지역비)]. 간격은 글리프보다 확실히 커야 한다. 붙으면 표가 아니라 노이즈로 읽힌다. */ const GRID = [