카피를 혜택 중심으로 고치고, 필터 데모를 실무 소재에 건다
All checks were successful
ci / build (push) Successful in 31s

지적 세 가지를 전부 확인했고 실제 결함도 하나 나왔다.

1. 재료 섹션 h2 가 아무것도 전달하지 않았다
   '표면은 CSS 가 정하고, 공간은 GPU 가 맡는다'
   금칙어도 없고 치환 테스트도 통과하는데 독자가 얻는 게 없다.
   구현 이야기를 대구로 포장한 것이었다.
   -> '텍스처 200KB 를 300바이트가 대신한다'

   카피라이팅 방법론(so you can 테스트 / 6~12단어 / 검증 가능한 수치)을
   찾아 적용했다. 우리 antipatterns 의 카피 절은 금지 목록뿐이라
   이런 문장을 못 잡는다 — 작법 절을 새로 넣었다.

2. 필터 데모가 줄무늬였다
   필터가 무엇을 하는지는 보이지만 왜 쓰는지는 안 보인다.
   각 필터를 실제로 걸리는 자리로 옮겼다.
     그레인  -> 매끈한 그라디언트 (없으면 밋밋하다는 대비)
     굴절    -> 글자 (유리 뒤가 실제로 휘는 것)
     듀오톤  -> 사진풍 면 (한 팔레트로 묶이는 것)
   타일마다 '언제 쓰는가' 한 줄을 추가했다.

3. 그레인 필터가 아무 효과도 못 내고 있었다
   feComposite in='SourceGraphic' in2='soft' operator='over' 는
   원본을 노이즈 위에 올린다. 노이즈가 통째로 가려진다.
   에러도 경고도 없이 조용히 죽는다.
   feBlend overlay 로 바꾸고 baseFrequency 0.9 -> 0.62.

섹션 간 검은 띠도 고쳤다. 파이프라인 캔버스가 섹션 패딩(112px) 아래에서
시작해 히어로 배경이 끝난 자리와 사이에 200px 이 비었다.

스킬
  antipatterns.md  카피 작법 — 금지 목록만으로는 못 잡는 실패
  svg-filters.md   데모는 실무 소재에 · feComposite 입력 순서 함정
This commit is contained in:
Yun Chan 2026-08-20 21:56:01 +09:00
parent 52fe0b5030
commit 61fb0cdcfa
6 changed files with 192 additions and 41 deletions

View file

@ -60,7 +60,7 @@ export interface Content {
eyebrow: string;
h2: string;
lead: string;
tiles: { id: "grain" | "glass" | "duotone"; name: string; how: string; cost: string }[];
tiles: { id: "grain" | "glass" | "duotone"; name: string; use: string; how: string; cost: string }[];
note: string;
};
metrics: { eyebrow: string; h2: string; lead: string; items: Metric[]; caveat: string };
@ -216,31 +216,34 @@ export const ko: Content = {
},
materials: {
eyebrow: "재료",
h2: "표면은 CSS 가 정하고, 공간은 GPU 가 맡는다",
h2: "텍스처 200KB 를 300바이트가 대신한다",
lead:
"아래 배경은 지금 셰이더가 그리고 있고, 세 장의 타일은 같은 줄무늬 하나에 서로 다른 SVG 필터를 먹인 것이다. 스킬이 다루는 재료를 글로 설명하는 대신 화면에서 돌린다.",
"필터는 장식이 아니라 재료다. 셋 다 실제로 쓰이는 자리에 걸어 뒀다 — 그레인은 매끈한 면에, 굴절은 유리 뒤 글자에, 듀오톤은 사진에. 배경은 지금 three.js 셰이더가 그리고 있다.",
tiles: [
{
id: "grain",
name: "종이",
how: "feTurbulence 로 만든 프랙탈 노이즈를 알파로 눌러 표면에 얹는다.",
cost: "비용 낮음 · 정지 상태",
name: "그레인",
use: "매끈한 그라디언트가 싸구려로 보일 때",
how: "feTurbulence 가 만든 프랙탈 노이즈를 알파로 눌러 표면에 얹는다. 이미지 요청이 한 건도 늘지 않는다.",
cost: "300바이트 · 정지 상태",
},
{
id: "glass",
name: "유리",
how: "난류를 변위 지도로 써서 굴절시킨다. 필터 영역을 넓혀 가장자리 잘림을 막는다.",
cost: "비용 중간 · 애니메이션 금지",
name: "굴절",
use: "유리 뒤의 글자가 실제로 휘어야 할 때",
how: "난류를 변위 지도로 써서 픽셀을 민다. 필터 영역을 넓혀야 가장자리가 잘리지 않는다.",
cost: "중간 · 애니메이션 금지",
},
{
id: "duotone",
name: "인쇄",
how: "명도만 남긴 뒤 feComponentTransfer 로 두 색 사이에 다시 매핑한다.",
cost: "비용 낮음 · 사진에도 쓴다",
name: "듀오톤",
use: "출처가 제각각인 사진을 한 팔레트로 묶을 때",
how: "명도만 남긴 뒤 feComponentTransfer 로 두 색 사이에 다시 매핑한다. 원본을 보정하지 않는다.",
cost: "낮음 · 사진에 쓴다",
},
],
note:
"위 세 장은 SVG 필터가 지금 먹고 있는 화면이고, 이 페이지의 배경 광은 three.js 셰이더가 그리고 있다. three 는 초기 번들에 없다. 첫 화면이 그려진 뒤에 받고, WebGL 이 없거나 모션을 줄이는 설정이면 아예 받지 않는다. 그때 보이는 그라디언트 폴백이 아니라 그대로 결과물이다.",
"이 셋은 SVG 필터가 지금 먹고 있는 화면이고, 그 뒤 배경은 three.js 셰이더가 그리고 있다. three 는 초기 번들에 없다 — 화면에 가까워질 때만 받고, WebGL 이 없거나 모션을 줄이는 설정이면 아예 받지 않는다. 그때 보이는 그라디언트 폴백이 아니라 그대로 결과물이다."
},
metrics: {
eyebrow: "이 페이지의 수치",
@ -447,31 +450,34 @@ export const en: Content = {
},
materials: {
eyebrow: "Materials",
h2: "CSS decides the surface. The GPU handles the space.",
h2: "300 bytes replaces a 200KB texture",
lead:
"The backdrop below is being drawn by a shader right now, and the three tiles are one striped fill wearing three different SVG filters. The skill's materials are running here rather than described.",
"Filters are material, not decoration. Each one sits where it is actually used — grain on a flat gradient, refraction behind glass over type, duotone on a photograph. The backdrop is a three.js shader, running now.",
tiles: [
{
id: "grain",
name: "Paper",
how: "Fractal noise from feTurbulence, pushed down through alpha and laid over the surface.",
cost: "low cost · static",
name: "Grain",
use: "when a smooth gradient reads as cheap",
how: "feTurbulence generates fractal noise, pushed down through alpha onto the surface. Not one extra image request.",
cost: "300 bytes · static",
},
{
id: "glass",
name: "Glass",
how: "Turbulence used as a displacement map. The filter region is widened so edges do not clip.",
cost: "medium cost · never animate it",
name: "Refraction",
use: "when type behind glass has to actually bend",
how: "Turbulence drives a displacement map that pushes pixels. Widen the filter region or the edges clip.",
cost: "medium · never animate",
},
{
id: "duotone",
name: "Print",
how: "Reduce to luminance, then remap between two colours with feComponentTransfer.",
cost: "low cost · works on photography too",
name: "Duotone",
use: "when photos from different sources need one palette",
how: "Keep luminance only, then remap between two colours with feComponentTransfer. The source file is untouched.",
cost: "low · use it on photos",
},
],
note:
"The three tiles above are SVG filters running right now, and the light behind this page is a three.js shader. three is not in the initial bundle. It arrives after the first paint, and never at all without WebGL or with reduced motion requested. What you see then is not a fallback. It is the result.",
"These three are SVG filters running on live pixels, and the field behind them is a three.js shader. three is not in the initial bundle — it arrives only as the section approaches, and never at all when WebGL is missing or motion is reduced. What you see then is not a fallback; it is the result."
},
metrics: {
eyebrow: "This page, measured",