[fix] landing: 모바일 대응 — 글리프 배율·밴드 리맵·헤더 겹침
- 글리프 크기를 화면 폭에서 파생. 격자 간격은 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>
This commit is contained in:
parent
87b896fc2f
commit
5917942fe3
@ -29,7 +29,7 @@ export function HeroDataFlow() {
|
||||
|
||||
{/* 분류 단계에서만 뜨는 무리 라벨. 아이콘만으로는 "무엇이" 분류됐는지 절반만 전달된다.
|
||||
좌표는 캔버스가 쓰는 값을 그대로 받아 써서 둘이 어긋날 일이 없다. */}
|
||||
<div aria-hidden className="absolute inset-0 pointer-events-none z-10">
|
||||
<div aria-hidden className="absolute inset-0 pointer-events-none z-10 hidden md:block">
|
||||
{CLUSTER_ANCHORS.map((anchor) => (
|
||||
<span
|
||||
key={anchor.key}
|
||||
@ -44,7 +44,7 @@ export function HeroDataFlow() {
|
||||
</div>
|
||||
|
||||
{/* 카피는 상단 55%, 데이터 흐름은 하단 밴드. 둘이 겹치지 않게 층을 나눈다. */}
|
||||
<div className="relative z-10 max-w-4xl mx-auto px-6 w-full text-center pt-[7vh]">
|
||||
<div className="relative z-10 max-w-4xl mx-auto px-6 w-full text-center pt-[104px] md:pt-[14vh]">
|
||||
{/* 히어로 카피에는 진입 애니메이션을 걸지 않는다.
|
||||
여기가 LCP 요소라 opacity:0 으로 시작하면 하이드레이션 전까지 첫 화면이 비고,
|
||||
광고 유입처럼 JS 가 느린 환경에서 그대로 손실이 된다. 모션은 배경 캔버스가 맡는다. */}
|
||||
@ -73,8 +73,8 @@ export function HeroDataFlow() {
|
||||
</div>
|
||||
|
||||
{/* 순환 링 — 정원. 점선 궤도만 돌고 마디·글자는 고정이다. */}
|
||||
<div className="relative z-10 mt-auto flex justify-center pb-6">
|
||||
<OrbitRing phase={phase} progressRef={progressRef} className="w-[300px] md:w-[340px] h-auto" />
|
||||
<div className="relative z-10 mt-auto flex justify-center pb-6 md:pb-6">
|
||||
<OrbitRing phase={phase} progressRef={progressRef} className="w-[250px] md:w-[290px] h-auto" />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
@ -58,8 +58,8 @@ export type DataFlowPhase = (typeof PHASES)[number]["key"]
|
||||
|
||||
/* 구도는 화면 하단 밴드에 앉힌다. 헤드라인·CTA 는 그 위에 서고, 데이터는 무대 바닥을
|
||||
흐르는 그림. 아래 목표 좌표는 전부 밴드 기준 지역 좌표(0..1)로 쓰고 band() 로 옮긴다. */
|
||||
const BAND_TOP = 0.50
|
||||
const BAND_H = 0.20 /* 아래쪽은 순환 링(정원) 자리로 비워둔다 */
|
||||
const BAND_TOP = 0.575
|
||||
const BAND_H = 0.155 /* 위는 카피, 아래는 순환 링(정원) 자리 */
|
||||
const band = (localY: number) => BAND_TOP + localY * BAND_H
|
||||
|
||||
/* 카메라 — 소실점과 원근 세기. draw() 와 링 좌표 계산이 같은 값을 써야 어긋나지 않는다. */
|
||||
@ -280,6 +280,13 @@ export function DataFlowCanvas({
|
||||
let height = 0
|
||||
let dpr = 1
|
||||
let lastPhase = -1
|
||||
/* 글리프 배율. 격자 간격은 dx × width 로 폭에 비례하는데 글리프가 CSS px 고정이면
|
||||
좁은 화면에서 셀보다 커져 서로 겹치고 덩어리가 된다. 같은 기준(폭)에서 파생시킨다. */
|
||||
let glyphScale = 1
|
||||
/* 좁은 화면에서는 카피가 길어져(CTA 가 세로로 쌓인다) 밴드가 CTA 를 침범한다.
|
||||
게다가 375px 에서 3군집을 나란히 두면 군집당 100px 남짓이라 어차피 판독이 안 된다.
|
||||
밴드를 링 뒤쪽으로 내리고, 군집 라벨은 히어로 쪽에서 접는다. */
|
||||
let narrow = false
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
@ -289,6 +296,8 @@ export function DataFlowCanvas({
|
||||
canvas.width = Math.round(width * dpr)
|
||||
canvas.height = Math.round(height * dpr)
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
glyphScale = Math.min(1, Math.max(0.3, width / 1180))
|
||||
narrow = width < 640
|
||||
}
|
||||
resize()
|
||||
|
||||
@ -356,9 +365,12 @@ export function DataFlowCanvas({
|
||||
const to = p.targets[nextIdx]
|
||||
const wobble = Math.sin(time * 0.0004 * p.speed + p.drift) * wobbleAmp
|
||||
const nx = from[0] + (to[0] - from[0]) * t + wobble
|
||||
const ny = from[1] + (to[1] - from[1]) * t + wobble * 0.7
|
||||
let ny = from[1] + (to[1] - from[1]) * t + wobble * 0.7
|
||||
const nz = from[2] + (to[2] - from[2]) * t
|
||||
|
||||
// 좁은 화면이면 밴드를 통째로 아래(링 뒤)로 옮긴다. 목표 좌표는 그대로 두고 여기서만 remap.
|
||||
if (narrow) ny = 0.6 + ((ny - BAND_TOP) / BAND_H) * 0.22
|
||||
|
||||
const k = perspective(nz)
|
||||
const sx = VANISH_X + (nx - VANISH_X) * k
|
||||
const sy = VANISH_Y + (ny - VANISH_Y) * k
|
||||
@ -379,7 +391,7 @@ export function DataFlowCanvas({
|
||||
const buckets = [new Path2D(), new Path2D(), new Path2D()]
|
||||
for (const i of sorted) {
|
||||
if (particles[i].type !== type) continue
|
||||
const r = Math.max(0.35, particles[i].dot * pk[i])
|
||||
const r = Math.max(0.35, particles[i].dot * pk[i] * Math.max(0.55, glyphScale))
|
||||
const path = buckets[bucketOf(ps[i] * (0.45 + 0.55 * pk[i]))]
|
||||
path.moveTo(px[i] + r, py[i])
|
||||
path.arc(px[i], py[i], r, 0, Math.PI * 2)
|
||||
@ -401,8 +413,8 @@ export function DataFlowCanvas({
|
||||
const [bw, bh] = GLYPH_SIZE[type]
|
||||
const r = particles[i].ratio
|
||||
// 품목은 너비가, 협력사는 높이가 개체마다 다르다 — 값이 있는 데이터처럼 보이게.
|
||||
const gw = (type === T_ITEM ? bw * r : bw) * pk[i]
|
||||
const gh = (type === T_PARTNER ? bh * r : bh) * pk[i]
|
||||
const gw = (type === T_ITEM ? bw * r : bw) * pk[i] * glyphScale
|
||||
const gh = (type === T_PARTNER ? bh * r : bh) * pk[i] * glyphScale
|
||||
ctx.globalAlpha =
|
||||
Math.min(1, TYPE_ALPHA[type] * particles[i].emphasis) * detail * ps[i] * (0.4 + 0.6 * pk[i])
|
||||
ctx.drawImage(sprites[type], px[i] - gw / 2, py[i] - gh / 2, gw, gh)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user