diff --git a/apps/site/src/components/Materials.astro b/apps/site/src/components/Materials.astro index 418cfc4..07d37fc 100644 --- a/apps/site/src/components/Materials.astro +++ b/apps/site/src/components/Materials.astro @@ -50,7 +50,6 @@ const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)"); c.demos.map((d, i) => (
  • {d.name}

    -

    {d.problem}

    {[false, true].map((on) => ( @@ -63,8 +62,11 @@ const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)"); ))}
    -

    {d.how}

    -

    {d.verdict}

    +
    +

    {d.problem}

    +

    {d.how}

    +

    {d.verdict}

    +
  • )) } @@ -165,7 +167,10 @@ const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)"); position: relative; max-width: 1180px; margin-inline: auto; - padding: var(--wide-pad); + /* --wide-pad 는 페이지 그리드의 바깥 칼럼을 정의하는 토큰이다(최대 10rem). + 여기에 그대로 쓰면 1440px 화면에서 좌우 160px 씩, 폭 320px 이 사라진다. + 그만큼 오른쪽 설명 칸이 170px 로 줄어 여덟 자마다 줄이 바뀌었다. */ + padding: var(--space-6); border-radius: var(--radius-frame); overflow: hidden; isolation: isolate; @@ -197,14 +202,26 @@ const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)"); } .fdemo + .fdemo { border-top: 1px solid var(--line); padding-top: var(--space-6); } - .fd-name { font-size: var(--step-1); } - .fd-problem { max-width: 52ch; color: var(--ink-muted); } + /* 데모와 설명을 세로로 쌓으면 한 항목이 320px 을 먹는다. + 넷이면 섹션이 3.1 화면 — 파이프라인 설명보다 길어졌다. + 그림은 왼쪽, 말은 오른쪽에 두면 같은 내용이 절반 높이에 들어간다. */ + .fdemo { + display: grid; + grid-template-columns: minmax(0, 584px) minmax(0, 1fr); + column-gap: var(--space-6); + align-items: start; + } + .fd-name { + grid-column: 1 / -1; + font-size: var(--step-1); + margin-bottom: var(--space-4); + } + .fd-problem { color: var(--ink-muted); } .fd-pair { display: grid; - grid-template-columns: repeat(2, minmax(0, 300px)); - gap: var(--space-4); - margin-block: var(--space-4); + grid-template-columns: repeat(2, minmax(0, 280px)); + gap: var(--space-3); } .fd-side { margin: 0; } .fd-tag { @@ -215,13 +232,16 @@ const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)"); .is-bad { color: var(--ink-muted); } .is-good { color: var(--accent); } - .fd-how { max-width: 60ch; } + .fd-text > * + * { margin-top: var(--space-3); } .fd-verdict { margin: var(--space-2) 0 0; font-size: var(--mono-size); color: var(--accent); } + @media (max-width: 1080px) { + .fdemo { grid-template-columns: 1fr; row-gap: var(--space-4); } + } @media (max-width: 720px) { .fd-pair { grid-template-columns: 1fr; } } diff --git a/apps/site/src/components/Pipeline.astro b/apps/site/src/components/Pipeline.astro index 1be42b5..4c68ea3 100644 --- a/apps/site/src/components/Pipeline.astro +++ b/apps/site/src/components/Pipeline.astro @@ -49,7 +49,6 @@ const { c } = Astro.props; )) } -

    {c.after}

    @@ -169,7 +168,7 @@ const { c } = Astro.props; space-7 까지 올렸더니 이번엔 정거장 사이가 224px 로 벌어져 목록이 흩어져 보였다 — 여섯 개가 한 목록으로 안 읽힌다. 읽기 간격과 스크롤 구간은 같은 손잡이가 아니다. - 간격은 읽기에 맞추고, 스크롤 구간은 스파인 뒤 여백(.after)에서 번다. */ + 간격은 읽기에 맞추고, 스크롤 구간은 스파인 뒤 여백에서 번다. */ padding-block: var(--stop-pad); display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); @@ -226,13 +225,11 @@ const { c } = Astro.props; overflow-wrap: anywhere; } - .after { - margin-top: var(--space-7); - max-width: var(--measure); - /* 여기가 스크롤 구간을 버는 자리다. 정거장 간격을 벌리는 대신 - 마지막 정거장 뒤에 여운을 둔다 — 스파인이 끝난 뒤라 빈 공간으로 읽히지 않는다 */ - padding-bottom: 62dvh; - } + /* sticky 게이트가 끝까지 돌려면 스파인 뒤에 스크롤 구간이 필요하다. + 한동안 여기에 마무리 문장을 한 줄 놓아 그 자리를 채웠는데, + 읽는 사람에게는 뜬금없는 문장이었다 — 여백이 필요하면 여백을 만든다. + 문장으로 여백을 대신하지 않는다. */ + .spine { padding-bottom: 62dvh; } @media (max-width: 780px) { .stop { diff --git a/apps/site/src/i18n/content.ts b/apps/site/src/i18n/content.ts index 0540ea1..c37f057 100644 --- a/apps/site/src/i18n/content.ts +++ b/apps/site/src/i18n/content.ts @@ -49,7 +49,6 @@ export interface Content { briefLabel: string; brief: string; stages: Stage[]; - after: string; }; showcase: { h2: string; @@ -208,8 +207,6 @@ export const ko: Content = { briefLabel: "들어온 브리프", brief: "수제 커피 로스터리의 원두 정기구독 랜딩 만들어줘", stages: STAGES_KO, - after: - "여섯 단계 끝에 남는 것은 페이지 하나와 design.md 하나다. 다음 사람이 그 파일을 읽고 같은 결정을 이어간다.", }, showcase: { h2: "결과물은 서로 닮지 않는다", @@ -237,38 +234,38 @@ export const ko: Content = { { id: "scrim", name: "글자 모양대로 생기는 스크림", - problem: "사진 위 캡션이 대비 4.5:1 을 못 넘는다. 사진마다 밝기가 달라 반투명 박스를 손으로 맞춰야 한다.", + problem: "사진 위 캡션이 대비를 못 넘긴다. 사진마다 밝기가 달라 반투명 박스를 손으로 맞추게 된다.", beforeLabel: "그냥 얹음", afterLabel: "filter: url(#scrim)", - how: "feMorphology 로 글자 알파를 부풀리고 블러한 뒤, 그 모양대로만 feFlood 를 채운다. 박스가 아니라 글자를 따라가므로 사진을 가리지 않는다.", - verdict: "흰 글자 대비 1.70:1 → 4.79:1 · 사진을 바꿔도 다시 맞출 것이 없다", + how: "글자 알파를 부풀려 그 모양대로만 색을 채운다. 박스가 아니라 글자를 따라가므로 사진을 덜 가린다.", + verdict: "흰 글자 대비 1.70:1 → 4.79:1", }, { id: "cvd", name: "색각 이상 시뮬레이션", - problem: "상태를 색으로만 구분하면 남성 12명 중 1명에게 같은 배지가 된다. 눈으로는 절대 못 잡는다.", + problem: "상태를 색으로만 구분하면 남성 12명 중 1명에게 같은 배지가 된다. 내 화면만 봐서는 못 잡는다.", beforeLabel: "내가 보는 화면", afterLabel: "적록색약(2형)", - how: "feColorMatrix 하나다. 이것만은 sRGB 로 두면 안 된다 — 색각 변환은 물리적 혼합이라 linearRGB 가 맞다. 필터 전체에서 유일한 예외다.", - verdict: "세 배지의 최소 색거리 82 → 2 · 주의와 실패가 같은 색이 된다", + how: "feColorMatrix 한 줄. 이것만은 linearRGB 로 둔다 — 색각 변환은 물리적인 빛의 혼합이다.", + verdict: "배지 셋의 최소 색거리 82 → 2 · 주의와 실패가 같은 색", }, { id: "goo", name: "떨어진 것이 하나로 뭉치기", - problem: "조각이 서로 녹아 붙는 표현을 하려고 캔버스나 물리 라이브러리를 켠다. 로더, 메뉴 병합, 인디케이터 전환에서 반복된다.", + problem: "조각이 녹아 붙는 표현에 캔버스나 물리 라이브러리를 켠다. 로더와 메뉴 전환에서 반복된다.", beforeLabel: "필터 없음 · 원 3개", afterLabel: "filter: url(#goo)", - how: "크게 블러한 뒤 feColorMatrix 로 알파만 18배 세워 잘라낸다. 붙을지 말지는 눈대중이 아니라 산수다 — 두 원 사이 중간점의 블러 알파가 문턱(7÷18 ≒ 0.39)을 넘어야 한다. 간격 18px 에 블러 7 로는 0.2 근처라 못 넘었고, 12px 에 10 으로 넘겼다.", - verdict: "가로로 세어 덩어리 3개 → 1개 · JS 0줄", + how: "블러한 뒤 알파만 세워 잘라낸다. 붙을지 말지는 취향이 아니라 간격과 블러 반경의 비율로 정해진다.", + verdict: "덩어리 3개 → 1개 · JS 0줄", }, { id: "glass", name: "배경을 휘게 하는 오버레이", - problem: "반투명 패널은 뒤를 흐리게만 만든다. 실제 유리는 빛을 휜다 — 그 차이가 패널을 화면 위가 아니라 화면 안에 놓는다.", + problem: "반투명 패널은 뒤를 흐리게만 만든다. 실제 유리는 빛을 휜다.", beforeLabel: "backdrop-filter: blur", afterLabel: "blur + url(#lens)", - how: "변위 지도를 backdrop-filter 에 넘긴다. R 이 X, G 가 Y 이동량이고 128 이 중립이다. 지도를 feTurbulence 로 만들면 값이 128 로 수렴해 아무 일도 일어나지 않는다 — 가장자리에서 안쪽으로 미는 그라디언트를 직접 그려야 한다.", - verdict: "가장자리에서 격자가 눌린다 · 지도 크기가 요소와 다르면 조용히 아무 일도 안 일어난다 · Chromium 전용", + how: "변위 지도를 backdrop-filter 에 넘긴다. 노이즈로 만들면 값이 중립으로 수렴해 아무 일도 안 일어난다.", + verdict: "가장자리에서 격자가 눌린다 · Chromium 전용", }, ], }, @@ -296,6 +293,7 @@ export const ko: Content = { h2: "이 페이지를 만들며 실제로 걸린 것", lead: "남을 검사한 결과가 아니라 자신을 검사한 결과다.", items: [ + { caught: "폰트가 교체되는 순간 글자 폭이 13% 튀고 있었다", fixed: @@ -326,39 +324,6 @@ export const ko: Content = { fixed: "밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다.", }, - { - caught: "320px 에서 가로로 13px 넘쳤다. 표를 스크롤 컨테이너로 감쌌는데도", - fixed: - "grid item 의 기본 min-width 는 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 을 걸어야 overflow 가 실제로 작동한다.", - }, - { - caught: "eyebrow 라벨이 6개. 카운트 규칙 상한은 3개", - fixed: "세 섹션만 남기고 나머지는 헤드라인만으로 구분했다. 라벨이 없어도 섹션은 읽힌다.", - }, - { - caught: "본문 컬럼이 960px. 우리가 정한 한 줄 길이는 561px", - fixed: "그리드의 본문 폭을 토큰에 묶었다. 스킬 문서의 예시 코드도 같이 고쳤다.", - }, - { - caught: "방향을 바꾸면서 이 스킬의 1단계를 건너뛰었다", - fixed: - "재질을 유리로 바꾸라는 요청은 2단계 방향 재결정이고, 그때는 전체 경로로 올려 레퍼런스부터 다시 봐야 한다. 그러지 않고 구현으로 바로 들어갔다. 결과는 이전 방향의 뼈대에 유리를 바른 페이지였고 design.md 는 옛 결정을 그대로 담고 있었다. 0단계부터 다시 돌았다.", - }, - { - caught: "유리판이 정면으로 서 있어 프레넬이 구조적으로 0이었다", - fixed: - "평면이 카메라를 정면으로 보면 법선과 시선의 내적이 1이라 가장자리 반사가 계산되지 않는다. 유리로 읽히게 하는 유일한 신호가 죽어 있었고, 그래서 판 여섯 장이 흐린 사각형으로 보였다. 법선을 미세하게 굽히고 테두리 광을 UV 경계에서 따로 만들었다.", - }, - { - caught: "페이지의 본체인 파이프라인이 화면의 43%만 쓰고 있었다", - fixed: - "1440px 중 625px. 아홉 섹션에서 가장 좁으면서 높이는 1637px 로 가장 길었다. 단계 설명과 산출물을 두 열로 열어 1113px 로 넓혔다.", - }, - { - caught: "대비를 재는 스크립트가 반투명 배경을 불투명으로 계산했다", - fixed: - "\"English 1.15:1\" 이라는 틀린 값이 나왔다. 알파를 아래에서 위로 합성하도록 고치자 실패 0건이었다. 검사 도구가 틀리면 통과도 실패도 의미가 없다.", - }, ], note: "열세 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 사람이 잡은 것이 셋이다. 카드가 전형적이라는 말, 폰트를 제대로 다루지 못한다는 말, 그리고 우리 스킬을 진짜로 쓴 게 맞냐는 말.", }, @@ -472,8 +437,6 @@ export const en: Content = { briefLabel: "The brief that came in", brief: "Build a subscription landing page for a small-batch coffee roastery", stages: STAGES_EN, - after: - "What remains after six stages is one page and one design.md. The next person reads that file and continues the same decisions.", }, showcase: { h2: "The outputs do not resemble each other", diff --git a/apps/site/src/pages/en/index.astro b/apps/site/src/pages/en/index.astro index f830a87..ddd60a7 100644 --- a/apps/site/src/pages/en/index.astro +++ b/apps/site/src/pages/en/index.astro @@ -13,14 +13,27 @@ import { en as c } from "../../i18n/content"; --- + {/* + 순서는 세 묶음이다. 한동안 기능과 증거가 번갈아 나와서 + (사례 → 필터 → 수치 → 감사 → 조판 → 규칙) 읽는 사람이 지금 무슨 이야기를 + 듣고 있는지 놓쳤다. 같은 성격끼리 붙인다. + + ① 무엇인가 히어로 · 파이프라인 + ② 무엇을 아는가 SVG 필터 · 한글 조판 + ③ 정말 그런가 사례 · 수치 · 자기 감사 · 규칙 + ④ 행동 설치 + */} - + + + + - + diff --git a/apps/site/src/pages/index.astro b/apps/site/src/pages/index.astro index edb1ca2..bf9bc50 100644 --- a/apps/site/src/pages/index.astro +++ b/apps/site/src/pages/index.astro @@ -13,14 +13,27 @@ import { ko as c } from "../i18n/content"; --- + {/* + 순서는 세 묶음이다. 한동안 기능과 증거가 번갈아 나와서 + (사례 → 필터 → 수치 → 감사 → 조판 → 규칙) 읽는 사람이 지금 무슨 이야기를 + 듣고 있는지 놓쳤다. 같은 성격끼리 붙인다. + + ① 무엇인가 히어로 · 파이프라인 + ② 무엇을 아는가 SVG 필터 · 한글 조판 + ③ 정말 그런가 사례 · 수치 · 자기 감사 · 규칙 + ④ 행동 설치 + */} - + + + + - +