From 1ed3c955425fb70f6eae514c13a636fbfeb05146 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Thu, 20 Aug 2026 15:57:40 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=A8=EB=9F=B4=EB=9E=99=EC=8A=A4=EB=A5=BC?= =?UTF-8?q?=20=EC=8B=A4=EC=A0=9C=EB=A1=9C=20=EB=B3=B4=EC=9D=B4=EA=B2=8C=20?= =?UTF-8?q?=EA=B3=A0=EC=B9=98=EA=B3=A0=20=EA=B2=A9=EC=9E=90=EB=A5=BC=20?= =?UTF-8?q?=EA=B1=B7=EC=96=B4=EB=82=B8=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "티가 안 난다"는 지적이 맞았다. 세 가지를 동시에 잘못하고 있었다. 1. 이동량이 모자랐다 여유를 28% 잡아놓고 92px 만 움직였다. 스크롤 1800px 구간에서 5%다. 공식은 이미지를 컨테이너보다 크게 만들고 초과분 전체를 움직이는 것. depth 0.45 -> 높이 145%, translateY(-31.03%). 같은 구간에서 374px. translateY(%) 는 자기 크기 기준이라 컨테이너 높이를 몰라도 맞는다. 2. overflow: hidden 이 view() 타임라인을 죽이고 있었다 overflow: hidden 은 스크롤 컨테이너를 만들고, 그러면 자식의 view() 가 문서가 아니라 그 상자를 기준으로 잡힌다. 이미지는 상자 안에서 늘 같은 자리라 진행도가 고정된다. 콘솔 에러도 없고 computed 값도 정상이라 두 스크롤 위치에서 transform 을 재보고서야 이동 0px 인 것을 알았다. 잘라내는 일은 섹션의 overflow: clip 에 맡겼다. 3. opacity 로 눌러 이미지를 지웠다 어두운 광원 사진을 opacity 0.5 로 깔면 검정도 빛도 같이 죽는다. mix-blend-mode: screen 으로 바꿨다 — 검정은 투명, 밝은 곳만 더해진다. 그래서 불투명도 0.85~1.0 을 써도 본문 대비가 안전하다. 생성 프롬프트도 같은 함정이었다. "어둡게"를 너무 강하게 넣어서 안전한 이미지가 아니라 없는 이미지를 만들었다. 격자 배경 제거 사진이 들어온 뒤로 배경이 두 겹이라 서로 경쟁했고 표로 읽혔다. 배경도 한 섹션에 하나다. 스킬 motion.md 에 넷 다 반영 이동량 공식 · overflow 함정 · screen 합성 · 배경은 한 겹 --- apps/site/design.md | 33 +++++++-- apps/site/src/components/BgPhoto.astro | 82 ++++++++++++++++++----- apps/site/src/components/Install.astro | 2 +- apps/site/src/components/Materials.astro | 2 +- apps/site/src/components/Metrics.astro | 2 +- apps/site/src/components/Pipeline.astro | 2 +- apps/site/src/components/Preflight.astro | 2 +- apps/site/src/components/Showcase.astro | 2 +- apps/site/src/components/Typography.astro | 2 +- apps/site/src/styles/base.css | 62 ++--------------- packages/skill/references/motion.md | 75 +++++++++++++++++++++ 11 files changed, 181 insertions(+), 85 deletions(-) diff --git a/apps/site/design.md b/apps/site/design.md index 9cf957a..83e4f4c 100644 --- a/apps/site/design.md +++ b/apps/site/design.md @@ -213,14 +213,35 @@ WebGL 게이트: `prefers-reduced-motion` 이면 **아예 시작하지 않는다 | 레이어 | 무엇 | 어디 | 근거 | |---|---|---|---| -| `.bg-grid` | 간격 눈금 격자. 셀이 `--grid-cell = --space-6` | 파이프라인 · 수치 | "모든 값이 토큰에서 나온다"가 배경에서도 성립해야 한다. 계기판의 눈금이기도 하다 | -| `.bg-glow` | 광원 블롭 두 개. 섹션마다 `--glow-x` 로 자리를 옮긴다 | 재료 · 설치 | 히어로의 빛이 페이지를 관통해 마지막 CTA 에서 돌아온다 | +기준면은 `BgPhoto` 컴포넌트가 만든다(아래 표). -격자는 가장자리를 마스크로 지운다 — 화면 끝까지 가면 배경이 아니라 **표**가 된다. -배경이 가장 적게 움직인다. 앞의 것보다 크게 움직이면 앞으로 튀어나와 보인다. +**CSS 격자를 한동안 같이 깔았다가 걷어냈다.** 사진이 들어온 뒤로 배경이 두 겹이 되어 +서로 경쟁했고, 격자는 배경이라기보다 **표**로 읽혔다. 배경도 한 섹션에 하나다. -**실측 3층** (스크롤 1000 → 1600): 격자 **+14.9px** · 산출물 **-14px** · 단계명 **0**. -한 층은 반드시 고정이다. 전부 움직이면 기준이 없어 그냥 흔들리는 페이지가 된다. +한 층은 반드시 고정이다 — 파이프라인의 단계명은 움직이지 않는다. +전부 움직이면 기준이 없어 그냥 흔들리는 페이지가 된다. + +### 패럴랙스가 세 번 실패한 경위 + +**1. 이동량이 모자랐다.** 여유를 28% 잡아놓고 `±46px`(총 92px)만 움직였다. +스크롤 1800px 구간에서 이동 비율 5% — 아무도 시차를 느끼지 못했다. +공식은 **이미지를 컨테이너보다 크게 만들고 그 초과분 전체를 움직이는 것**이다. +`depth 0.45` → 이미지 145%, 이동 `translateY(-31.03%)`. 같은 구간에서 **374px**. + +**2. `overflow: hidden` 이 `view()` 타임라인을 죽이고 있었다.** +`overflow: hidden` 은 스크롤 컨테이너를 만들고, 그러면 자식의 `view()` 가 문서가 아니라 +그 상자를 기준으로 잡힌다. 이미지는 상자 안에서 늘 같은 자리라 진행도가 고정된다. +**콘솔 에러도 없고 computed 값도 정상이라** 두 스크롤 위치에서 `transform` 을 재보고서야 +이동 0px 인 것을 알았다. 잘라내는 일은 섹션의 `overflow: clip` 에 맡겼다 — +`clip` 은 스크롤 컨테이너를 만들지 않는다. + +**3. `opacity` 로 눌러 이미지를 지웠다.** 어두운 광원 사진을 `opacity: 0.5` 로 깔면 +검정도 빛도 같이 죽어 **있는지조차 보이지 않는다.** `mix-blend-mode: screen` 으로 바꿨다 — +검정은 투명해지고 밝은 곳만 더해지므로 불투명도를 0.85~1.0 으로 유지해도 본문 대비가 안전하다. +밝은 종이만 `normal` 이다. + +**생성 프롬프트에도 같은 함정이 있었다.** "본문 뒤에 깔리니 어둡게"를 너무 강하게 넣어서 +안전한 이미지가 아니라 **없는 이미지**를 만들었다. 대비는 합성 방식으로 풀 문제였다. ### 배경 사진 넉 장 (gpt-image-2 로 생성) diff --git a/apps/site/src/components/BgPhoto.astro b/apps/site/src/components/BgPhoto.astro index 1af18c3..ee6f01f 100644 --- a/apps/site/src/components/BgPhoto.astro +++ b/apps/site/src/components/BgPhoto.astro @@ -18,60 +18,108 @@ const SOURCES = { refraction, paper, split, align } as const; interface Props { variant?: keyof typeof SOURCES; - /** 0~1. 본문 뒤에 깔리므로 기본값을 낮게 둔다 */ + /** + * 패럴랙스 강도. 이미지를 컨테이너보다 이 비율만큼 크게 만들고, + * 스크롤 구간 동안 정확히 그 초과분만큼 움직인다. + * 0.45 면 섹션 높이가 2000px 일 때 900px 을 이동한다. + */ + depth?: number; + /** 0~1 */ opacity?: number; /** 좌우 반전. 같은 그림을 두 섹션에 쓸 때 되풀이로 안 읽히게 한다 */ flip?: boolean; } -const { variant = "refraction", opacity = 0.45, flip = false } = Astro.props; +const { variant = "refraction", depth = 0.45, opacity = 0.9, flip = false } = Astro.props; const src = SOURCES[variant]; + +// 밝은 종이만 그대로 얹고, 어두운 광원 사진은 더하기 합성한다. +const additive = variant !== "paper"; + +// 이미지 높이 = (1 + depth) 배. 이동량은 이미지 자기 높이 기준 비율로 환산한다. +// 예: depth 0.45 -> 높이 145%, 이동 -(0.45 / 1.45) = -31.03% +const scale = 1 + depth; +const shift = -(depth / scale) * 100; --- {/* - `loading="lazy"` — 초기 번들에 들어가지 않는다. CSS background-image 로 깔면 이 통제권을 잃는다(항상 받고, 최적화도 안 된다). 그래서 를 절대배치한다 - `aria-hidden` + 빈 alt — 장식이다. 보조기술이 읽을 것이 없다 - - opacity 기본 0.45 — 본문 대비를 지키는 선. 올리려면 그 위 글자를 다시 재라 */} -