130 KiB
design.md — designpaca 소개 페이지
6단계 산출물. 다음 실행(사람이든 에이전트든)은 이 파일을 먼저 읽고 같은 결정을 이어간다.
개정 이력
| 날짜 | 판 | 무엇이 바뀌었나 |
|---|---|---|
| 2026-08-20 | 초판 | swiss-minimal · three.js 미사용 · 라이트 기반 |
| 2026-08-20 | 2판 | dark-instrument · three.js 채택 · 전체 경로 재주행 |
| 2026-08-23 | 현행 | 쇼케이스 히어로 리뉴얼 — Three.js 추상 FBM 셰이더 캔버스(ShowcaseHeroCanvas), 한국어 에디토리얼 카피 개편, 15개 라이브 쇼케이스 및 디테일 페이지 서브링크 연동 |
초판이 거짓말이 됐던 경위를 남긴다. 사용자가 "글래스모피즘·리퀴드글래스·히어로에 힘"을 요청했을 때
그것은 2단계 방향 재결정이었고, 스킬은 그럴 때 전체 경로로 올려 1단계부터 다시 돌라고 씌어 있다.
그러지 않고 4단계 구현으로 바로 들어갔다. 레퍼런스 없이 재질만 갈아끼운 결과가 스위스 미니멀 뼈대에
유리를 바른 페이지였고, design.md 는 초판 상태로 남아 프리셋·이펙트·형태 수치가 전부 어긋났다.
2판은 0단계부터 다시 돌았다.
브리프 3줄
- 무엇을: designpaca 소개 페이지. 파이프라인 스킬 + npx 설치 CLI 를 소개하는 단일 페이지, 한국어·영어 두 판
- 누구에게: Claude Code · Codex · Cursor 를 쓰는 개발자. 이 페이지를 보고
npx designpaca를 터미널에 치게 만드는 것이 목표 - 제약: Astro 정적 → Cloudflare Pages. 이 페이지 자체가 스킬의 품질 증거물이다 — 스킬이 금지하는 것을 이 페이지가 하면 안 된다
레퍼런스 (전부 실측. computed style 추출까지 돌렸다)
| 슬롯 | 사이트 | 업종 | 실측값 | 가져온 원리 | 가져오지 않은 것 |
|---|---|---|---|---|---|
| R1 · 구조 | zed.dev | 코드 에디터 | 컨테이너 1120px(68회) ↔ 전체폭 1425px(50회) 교대 · 본문 칼럼 512px · radius 2px 가 621회 · 모노 524회로 지배 | 폭의 교대, 각진 형태 어휘, 모노를 정체성으로 | 세리프 h1(우리는 한글) · 최대÷본문 3.0배(위계가 약하다) · 섹션 11개 |
| R2 · 톤 | lusion.co | 3D 프로덕션 스튜디오 | 배경 흰색 · 텍스트 색 2단계(검정 135 / 흰 16) · 폰트 1개(Aeonik) · 강조색 rgb(0,22,236) 4회 · 캔버스는 radius 15px 액자 안 · h1 이 36px | 3D 는 액자 안, 텍스트는 액자 밖. 3D 가 크면 타이포는 물러난다. 색은 캔버스 안에서만 소리친다 | 흰 배경 자체 · 파랑 |
| R3 · 디테일 | Codrops "Inside HAOQI.DESIGN" | — | 축 5 전용 | DOM 이 구조를 소유하고 WebGL 은 셰이더 uniform 만 만진다. 한 rAF 에서 스크롤을 공유하고 뷰포트 밖에서는 정지 | R3F/Lenis 스택 |
R3 구현 제약: 셰이더 uniform 만 갱신하므로 transform/opacity 외의 CSS 를 애니메이션하지 않는다(게이트 #8 안전).
뷰포트 밖 렌더 정지가 필수 조건이다.
접근 실패 기록 — iglooinc.com 은 hugedomains 파킹으로 302, lasvit.com 은 403.
reference-method.md §3-b 가 경고한 그대로였다. 왕복 세 번에서 끊고 우회했다.
히어로 국소 레퍼런스 (1′)
R1~R3 로 구조를 잡은 뒤에도 히어로가 어색해서 국소 브리프 표로 두 곳을 더 실측했다.
design.md 는 "3D 는 액자 안"까지만 답하고 액자가 화면의 몇 %여야 하는지는 답하지 않는다.
| 사이트 | 3D 영역 | 텍스트 위치 | h1 |
|---|---|---|---|
| lusion.co | 1440×645 = 첫 화면의 71% | 캔버스 위, 조밀하게 | 36px |
| basement.studio | 1425×900 = 100% | 캔버스 아래(y=916) | 87px, weight 600 |
| railway.com | SVG 1062×755, h1 아래 | 중앙 상단 | 54px, 1줄 |
| 우리(고치기 전) | 1352×465 = 52% | 위 380px | 41.5px |
셋 다 3D 와 텍스트를 세로로 완전히 분리하고, 3D 가 첫 화면의 70% 이상을 먹는다. 우리는 텍스트 43% / 3D 52% 로 둘 다 어중간했다. basement 형(첫 화면 전체 3D)은 CTA 가 첫 화면에서 밀려나므로 전환이 목적인 우리에게 맞지 않는다. lusion 비율을 택했다.
현행: 텍스트 24% · 액자 59% · 캡션과 링크가 액자 아래 한 줄. 링크(저장소·npm)를 텍스트 블록에서 액자 캡션 줄로 내려 상단을 비웠다.
레퍼런스를 보기 전에 네 번 고쳤다. 판 폭 7.4 → 15, 액자 비율 16/7 → 16/5.5 → 16/6.6 → 16/6, sway 0.5 → 0.9 → 0.16. 전부 계산이지 근거가 아니었다. 레퍼런스 두 곳을 재자 한 번에 정리됐다.
한 문장 컨셉
검사대 위의 물체. 액자가 게이트고, 안에 든 것이 검사 중인 디자인이고, 밖의 글자가 판정 기록이다.
프리셋 — dark-instrument
초판의 swiss-minimal 에서 바꿨다. 페이지가 이미 어둡고 제품이 CLI 다.
초판에는 이 재질을 정당화하는 프리셋이 아예 없었다 — 그것이 "유리를 발랐는데 유리일 이유가 없다"의 원인이다.
프리셋은 "글래스모피즘은 내비게이션 바 정도까지"라고 반대한다. 사용자의 명시적 지시가 프리셋보다 위이므로 없애지 않고 의미가 생기는 자리로 옮겼다: 유리는 액자에만 쓴다 — 헤더 · 검사대 프레임 · 명령창. 콘텐츠 카드(브리프, 조판 데모)에서는 뺐다. 유리는 무언가를 담을 때만 의미가 있다.
감수한 리스크 — 하나
히어로에서 거대 타이포를 버리고 힘을 3D 액자로 넘긴다.
초판 히어로의 --display 는 최대 112px 로 화면 높이의 절반을 먹었고, 그 아래 캔버스가 깔려 있었다.
R2 의 h1 은 36px 인데 히어로가 압도적이다 — 힘이 타이포가 아니라 물체에서 나오기 때문이다.
현행 히어로 h1 은 clamp(var(--step-2), 1.1rem + 2.4vw, var(--step-4)), 최대 41.5px 다.
페이지에서 가장 큰 글자(96px)는 히어로가 아니라 수치 섹션에 있다 — R2 도 그렇다.
토큰
src/styles/tokens.css 전문. 초판에서 바뀐 것만 적는다.
- radius 2종:
--radius-sm: 3px(요소) ·--radius-frame: 14px(액자). 근거는 R1 의 2px(621회)과 R2 의 15px.999pxpill 3곳을 없앴다 — 계기판에 알약은 없다 --glow-2:#6E7BFF→#3D7BD6. 앞의 값은 R 이 높아 보라-파랑으로 읽힌다.antipatterns.md의 슬롭 지문이자dark-instrument가 "어두운 배경일수록 빠지기 쉬운 함정"으로 지목한 색이다- 유리 색값을 전부 토큰으로:
--glass-sheen·--glass-rim·--core-hi.base.css에 흩어져 있던rgb(255 255 255 / …)7곳이 하드 게이트 #1 위반이었다 - 이중 베젤(
.shell/.core) 폐기 → 단일.frame. 껍데기와 속의 radius 가 서로 달라 그것만으로 형태 어휘가 두 종 늘어난다 --step--1: 0.8125rem추가. 게이트 판독기가 스케일 밖 13px 을 쓰고 있었다- 강조색은 여전히 딥 틸 하나. 라이트/다크 쌍(
--accent/--accent-inverse)은 규칙으로 정의된 반전이라 1종으로 센다
타입 스케일 실측: step--1 ~ step-4 로 6단계, --display(96px)는 스케일 밖, 모노는 본문의 0.9375배 파생.
최대÷본문 = 5.6배. 초판 페이지는 "5단계"라고 실었는데 실측은 9종이었다 — 아래 8·9번 참조.
성능 — 예산과 실측
예산을 올렸고, 올렸다고 말한다. 초판 예산은 히어로까지 JS 50KB 였다. 이 페이지는 three.js 를 파는 제품의 데모이므로 그 판단을 검증할 수 있어야 한다.
예산 숫자 자체를 한 번 고쳤다. 처음 three.js 예산을 160KB 로 적었는데 실측이 184KB 였다. three.js 크기는 미리 알 수 있는 값이므로 이건 예산 설정 실패다. 190KB 로 고쳤고, 사후 조정이라는 사실을 남긴다. 실측을 보고 예산을 맞추는 것은 예산이 아니다 — 다음 실행은 여기서부터 시작해야 한다.
| 항목 | 예산 | 실측 | 판정 |
|---|---|---|---|
| 히어로까지 JS, 초기 (gzip) | 50KB | 복사 버튼 314B + 씬 부트 | 통과 |
| three.js, 지연 로드 (gzip) | 190KB | 184KB (동적 import(), IntersectionObserver 로 화면에 들어올 때만) |
통과 |
| 첫 인터랙션 | 2초 | 미측정 | 배포 후 Lighthouse |
| LCP / CLS | 2.5초 / 0.1 | 미측정 | 배포 후 |
WebGL 게이트: prefers-reduced-motion 이면 아예 시작하지 않는다. DPR 1.5 클램프.
hardwareConcurrency <= 4 면 판을 6장에서 3장으로 줄인다. 탭이 숨거나 화면 밖이면 그리지 않는다.
채택한 이펙트와 폴백
| 이펙트 | 구현 | 폴백 |
|---|---|---|
| 검사대 3D | 유리판 6장이 깊이 축에 서고 카메라가 스크롤로 통과. 굴절은 렌더 타겟 1장 샘플링(transmission 재질보다 훨씬 싸다) |
캔버스 아래 CSS 그라디언트가 폴백이 아니라 결과물이다. 액자·판독기·캡션은 그대로 남는다 |
| 게이트 판독기 | 통과한 판이 바뀔 때만 DOM 을 쓴다. 프레임마다 쓰지 않는다 | JS 없으면 게이트 01 / 06 정적 표시 |
| 복사 버튼 | 인라인 314B | 클립보드 실패 시 텍스트를 선택 상태로 만든다 |
5단계에서 실제로 걸린 것
정적 검사와 실렌더를 둘 다 돌렸다. 걸린 것을 전부 적는다 — 유리한 것만 고르면 리스크가 자기 홍보가 된다.
- 하드 게이트 #3 위반 — radius 4종(
sm/md/shell/999px). 2종으로 정리 - 하드 게이트 #1 위반 —
base.css에 토큰 밖rgb()7곳. 토큰으로 올림 - 하드 게이트 #7 위반 — 헤더 "English" 버튼 2.59:1.
--ink-muted→--ink로 올려 통과 - 카운트 규칙 위반 — 히어로 h1 이 4줄이었다(상한 2줄). 폰트 크기를 낮춰 2줄
- 셰이더 결함 — 판이 정면으로 서 있어
dot(n, view) ≈ 1, 프레넬이 구조적으로 0이었다. 유리로 읽히게 하는 유일한 신호가 죽어 있었고 그것이 "그냥 흐릿한 사각형"의 원인이다. 법선을 미세하게 굽히고 UV 경계로 테두리 광을 따로 만들어 해결 - 셰이더 UV 오류 —
gl_FragCoord를 렌더 타겟 크기(캔버스의 0.6배)로 나누고 있었다. UV 가 1 을 넘어 텍스처가 잘렸다.getDrawingBufferSize()로 교정 - 폭 리듬 부재 — 9개 섹션 중 파이프라인이 1440px 중 625px(43%) 만 쓰면서 높이는 1637px 로 가장 길었다. 페이지의 본체가 가장 좁았다. 2열로 열어 1113px
- 페이지가 실은 수치가 틀렸다 — "타입 단계 5단계" 라고 실었는데 실측 9종이었다. 이 페이지의 리스크가 "자기 수치를 공개한다" 인데 공개한 값이 틀리면 리스크가 아니라 허위다
lang속성이 타입 스케일을 새게 했다 — 언어 전환 링크에lang="en"이 붙어 있고(스크린리더가 "English" 를 영어로 읽어야 하므로 옳다)[lang]셀렉터가 스케일을 통째로 재정의해 그 링크만 16px 이 됐다. 접근성 표시가 조용히 디자인 토큰을 바꾸고 있었다. 크기는 헤더에서 확정하고 링크는 상속만 받게 고쳤다
측정 스크립트 자체의 오류도 한 번 있었다. 대비 계산에서 반투명 배경을 불투명으로 취급해 "English 1.15:1" 이라는 틀린 값이 나왔다. 알파를 합성하도록 고친 뒤 실패 0건. 검사 도구가 틀리면 통과도 실패도 의미가 없다.
최종 판정 (1440px · 320px 실렌더)
- 하드 게이트 12개 전부 "아니오"
- 대비: 본문·링크·버튼 전 요소 AA 이상, 실패 0건
- 320px: 가로 스크롤 없음(문서 폭 305px), 뷰포트 초과 요소 0, 버튼 줄바꿈 0
- 카운트: 섹션 9 / eyebrow 3(상한
ceil(9/3)=3) / 마퀴 0 / 히어로 헤드라인 2줄 / radius 어휘 2종 / 반투명 패널 2개 - 슬롭 지문 grep: 0건
의도적으로 하지 않은 것과 그 이유
- "Anti-Slop" 이라는 명명 — tasteskill 이 쓰는 말이다. 같은 단어를 쓰면 즉시 아류로 읽힌다
- 경쟁 제품의 실측치를 페이지에 싣기 — 남의 수치는 반박 가능하다. 우리 수치만 쓴다
- GitHub 스타 등 인기 지표 — 우리는 숫자가 0이다. 인기 대신 규율을 증거로 쓴다
- 3D 위에 본문 얹기 — R2 가 준 원리의 핵심이다. 액자 위에 얹는 글자는 판독기 하나뿐이고, 그것도 바탕을 깔았다
- GLB 모델 — 조사 결과 추상 지오메트리가 이 브리프에서 더 낫다. 모델 하나가 수 MB 이고, 우리가 파는 것은 모델이 아니라 절차다
- 다크를 "멋있어서" 고르기 — 프리셋이 다크 기본값을 슬롭 시그니처 1위로 지목한다. 제품이 터미널이라는 근거로 고른다
- 언어 자동 리다이렉트 / 국기 아이콘 — 사용자를 놀라게 하고, 언어는 국가가 아니다
- 두 판을 번역으로 만들기 — 각 언어판을 따로 썼다
3D 의 역할 — 주인공이 아니라 재질
한 번 액자 안에 넣었다가 되돌렸다. R2(lusion.co)의 "3D 는 액자 안"은 3D 자체가 상품일 때의
규칙이다. 그 스튜디오는 3D 를 팔기 때문에 액자가 쇼케이스 프레임으로 정당하다.
우리는 절차를 파는데 액자 안에 넣을 상품이 없어서, 보는 사람이 "저 상자는 뭐냐"고 묻게 됐다.
게다가 의미를 게이트 01 / 06 판독기가 자막으로 설명하고 있었다 — 그림이 스스로 말하지
못해서 자막을 단 것이다.
이 씬의 역할은 재질이다. 공간을 만드는 표면이지 볼거리가 아니다. 그래서 규칙이 반대다:
| 주인공(액자) | 재질(배경) — 현행 | |
|---|---|---|
| 테두리 광 | 0.85 | 0.42 |
| 알파 | 0.72 | 0.34 |
| UV 경계 | smoothstep(0, 0.05) |
smoothstep(0, 0.17) + 제곱 |
| 카메라 이동 | 전 구간 | 60% |
| 텍스트 | 액자 밖 | 위에 앉되 스크림으로 자리를 눌러줌 |
ResizeObserver 로 캔버스를 따라가게 했다. window.resize 만 듣던 동안 히어로 높이 변화를
놓쳐 배경이 화면의 3분의 2만 덮고 아래가 검게 비었다.
패럴랙스 지도 (실측)
깊이가 다른 층을 겹쳐 시차를 만든다. 한 층은 반드시 고정이다 — 전부 움직이면 기준이 없어 그냥 흔들리는 페이지가 된다.
| 자리 | 층 | 방식 | 실측 이동 |
|---|---|---|---|
| 히어로 배경 | 셰이더 흐름 필드 | scroll(root) |
+56px ↓ |
| 히어로 글자 | scroll(root) |
−88px ↑ (반대 방향) | |
| 파이프라인 | 게이트 씬(three.js) | sticky + 카메라 |
6단계를 통과 |
| ㅤ└ 단계명 | — | 0 고정 | |
| ㅤ└ 산출물 | view() |
±14px | |
| 재료 · 설치 · 프리플라이트 | 배경 사진 | fixed |
0 — 원경 |
| 사례 | 배경 사진 | drift 0.4 |
이미지 높이의 28.6% |
| 조판 | 종이 | drift 0.25 |
−122px (근경) |
| 재료 타일 | view() |
±26 / ±58px | |
| 프리플라이트 항목 | view() |
6 → 10px (뒤일수록 늦게) | |
| 수치 | 없음 | 의도적 정지 |
세 가지 깊이가 다 있다 — 원경(fixed) · 중경(drift) · 근경(콘텐츠와 함께). 종이를 원경으로 두지 않은 이유는 손에 닿는 거리의 재질이기 때문이다.
모바일(390px)에서도 fixed 가 유지된다. background-attachment: fixed 를 안 쓴 덕이다.
prefers-reduced-motion 에서는 게이트 씬이 아예 시작하지 않고, 고정 배경은
position: absolute 로 흐름에 돌아가며, 모든 view() 애니메이션이 꺼진다.
모션 문법 — 섹션마다 다른 말
같은 패럴랙스를 전부에 걸면 벽지다. 각 섹션의 주장을 움직임으로 옮겼다.
| 섹션 | 주장 | 움직임 |
|---|---|---|
| 히어로 | 결정하지 않으면 기본값이 나온다 | 글자 ↑ / 배경 ↓ — 반대 방향이라야 시차가 보인다 |
| 파이프라인 | 단계를 밟으면 결과가 나온다 | 단계명 고정, 산출물만 지연 |
| 사례 | 같은 스킬에서 다른 결과 | 두 판이 다른 속도 |
| 재료 | 표면은 한 겹이 아니다 | 타일이 서로 다른 깊이 |
| 프리플라이트 | 시간순 기록 | 뒤 항목일수록 늦게 |
| 수치 | 공개한 값은 검증 가능하다 | 없음 — 의도적 정지 |
마지막 줄이 결정이다. 숫자가 스크롤에 흔들리면 그 값도 흔들려 보인다.
배경 레이어 — 패럴랙스의 기준면
배경이 단색이면 패럴랙스는 성립하지 않는다. 앞의 것이 몇 px 어긋나도 비교할 대상이 없다. 히어로 말고는 기준면이 없어서 시차가 안 보였다.
배경 이미지를 사 오는 대신 이 페이지가 이미 주장하는 것을 그렸다.
| 레이어 | 무엇 | 어디 | 근거 |
|---|---|---|---|
기준면은 BgPhoto 컴포넌트가 만든다(아래 표). |
CSS 격자를 한동안 같이 깔았다가 걷어냈다. 사진이 들어온 뒤로 배경이 두 겹이 되어 서로 경쟁했고, 격자는 배경이라기보다 표로 읽혔다. 배경도 한 섹션에 하나다.
한 층은 반드시 고정이다 — 파이프라인의 단계명은 움직이지 않는다. 전부 움직이면 기준이 없어 그냥 흔들리는 페이지가 된다.
backdrop-filter 오판을 정정했다
"backdrop-filter 에 SVG 를 얹으면 Chrome 이 선언 전체를 버린다" 고 적어두고
페이지의 프리플라이트 로그에까지 실었다. 사실이 아니었다.
Chrome 151 에서 재측정: computed 값이 none 이 아니라 url("#id") 로 유지되고,
대비 높은 배경 위에서 실제로 굴절된다. 진짜 원인은 feImage 의 width/height 를
요소 크기와 맞추지 않은 것이었다 — backdrop-filter 는 필터 이미지를 자동으로 조정하지 않는다.
아직 헤더에 얹지 않은 이유는 브라우저 탓이 아니라 비용이다. Chrome 전용이고, 요소가 리사이즈될 때마다 변위 맵을 다시 만들어야 한다. 헤더는 폭이 계속 변한다.
한 번 실패한 것을 '안 되는 것'으로 적으면 그 문장이 다음 사람의 상한선이 된다. 로그 항목을 그 내용으로 바꿔 실었다.
패럴랙스를 "고정"으로 바꿨다
세 번 고친 뒤에도 티가 나지 않았다. 원인은 방식 자체였다 — 느리게 같이 움직이는 것으로는 원경이 안 된다. 속도만 다를 뿐 방향이 같기 때문이다.
배경을 뷰포트에 고정한다. background-attachment: fixed 는 iOS Safari 가 throttle 하므로
쓰지 않고, 섹션에 clip-path: inset(0) 을 걸어 position: fixed 의 컨테이닝 블록으로 만든다.
그러면 배경이 화면에 멈춘 채 그 섹션 안에서만 보인다.
실측: 스크롤 1000px 구간에서 배경의 viewportTop 이 계속 0.
대가가 둘 있고 둘 다 받아들였다.
- 조상에
transform·filter·mask를 못 쓴다. 그것들도 컨테이닝 블록을 만들어 고정을 깨뜨린다. 그래서 위아래 페이드 마스크를 포기하고 경계는 clip 으로 딱 자른다 prefers-reduced-motion에서는position: absolute로 되돌린다
전부 고정하지는 않았다. 원경과 중경을 나눴다.
| 섹션 | 모드 | 왜 |
|---|---|---|
| 재료 · 설치 · 프리플라이트 | fixed |
원경. 콘텐츠만 그 위를 지나간다 |
| 사례 | drift 0.4 |
두 결과물이 흐르는 자리라 배경도 같이 흐른다 |
| 조판 | drift 0.25 |
종이는 손에 닿는 거리다. 원경으로 두면 거짓말이 된다 |
유리 테두리는 conic 이 아니라 linear 로 돈다
헤더의 헤어라인이 변마다 끊겨 보였다. conic-gradient 로 만들었기 때문이다.
conic 은 중심에서 각도로 도는데, 1180×54 처럼 납작한 형태에서는 각도가 급변해서
긴 변에서 색이 거의 안 변하고 짧은 변에서 한꺼번에 바뀐다. 실제로 아래쪽만 절반 밝았다.
linear-gradient(100deg, …) 로 바꾸면 네 변이 같은 그라디언트를 공유해
테두리를 따라 이어진다. 마스크로 1px 만 남기는 구조는 그대로다.
그리고 아주 얕은 글로우를 얹었다 — 0 0 22px -8px 에 강조색을 42% 섞었다.
유리가 차갑게만 보이지 않게 하는 정도이고, 밝은 밴드에서는 흰빛 대신
그림자로 뒤집는다(--glass-rim·--glass-bloom 을 .band-inverse 에서 재정의).
테두리 광을 회전시켰다
정지한 헤어라인 위에 도는 빛을 얹었다. conic 의 각도를 @property 로 돌리면
매 프레임 배경을 다시 칠하므로(하드 게이트 #8), 정사각형 원뿔 레이어를 마스크 안에서
rotate 시킨다. 움직이는 것은 transform 뿐이다.
aspect-ratio: 1 로 정사각형을 유지해야 회전 중 밝기가 일정하고,
width: 200% 로 크게 잡아야 1178×52 짜리 헤더에서도 모서리까지 닿는다. 주기 11초.
three.js 모바일 대응
세로 화면에서 게이트가 통째로 안 보이고 있었다. 수직 시야는 화면 비율과 무관하지만 수평 시야는 aspect 에 비례한다 — 데스크톱(2.5:1)에서 정한 판 폭 15 가 세로 화면(0.45:1)의 수평 시야 2.5 에 비하면 여섯 배라, 첫 판이 화면을 완전히 덮었다.
fit() 에서 camera.aspect 를 갱신할 때 판의 scale.x 도 같이 잡는다.
그리고 저사양 판정을 고쳤다. hardwareConcurrency <= 4 만 보면 요즘 폰이 전부
고사양으로 잡힌다(8코어가 흔하다). innerWidth < 760 을 함께 본다 —
좁은 화면은 대개 배터리로 돌고 열이 더 난다. DPR 도 1.5 → 1.25,
캔버스 불투명도도 0.7 → 0.45(세로에서는 화면 전체를 덮으므로).
three.js 씬 셋은 서로 다른 것이어야 한다
게이트 씬을 파이프라인으로 옮기면서 히어로에도 그대로 남겨뒀더니 같은 그림이 페이지에 두 번 나왔다. 히어로를 다른 것으로 바꿨다.
| 자리 | 씬 | 무엇 |
|---|---|---|
| 히어로 | shader-plane 흐름 필드 |
노이즈를 두 번 접어 빛이 유리를 지난 결을 만든다. 공간을 만드는 재질 |
| 파이프라인 | gate-scene 유리판 여섯 장 |
스크롤하면 통과하고 그 단계가 켜진다. 의미를 가진 주인공 |
| 재료 | shader-plane 스테이지 |
SVG 필터 타일이 앉는 무대 |
히어로에는 무거운 씬을 임포트하지 않는다 — three.md §0 이 권하는 대로
셰이더 플레인 하나다. 파일 이름도 hero-scene.ts → gate-scene.ts 로 고쳤다.
이름이 실제 용도와 다르면 다음 사람이 히어로 것인 줄 알고 고친다.
캔버스 배경 위 본문 대비 실측(스크린샷 픽셀): 최악 8.46:1(가장 밝은 중앙 여백), h1 자리 14.83:1.
readPixels로 재려다 전부 0 이 나왔다.preserveDrawingBuffer: false면 버퍼가 비워져 있다. WebGL 위 대비는 스크린샷 픽셀로 재라.
게이트 씬을 파이프라인으로 옮겼다
히어로에서 뺐던 유리판 여섯 장을 여기로 되살렸다. 같은 씬인데 자막이 필요 없어졌다.
| 히어로에 뒀을 때 | 파이프라인으로 옮긴 뒤 | |
|---|---|---|
| 옆에 있는 것 | 헤드라인과 CTA | 여섯 단계의 실제 이름 |
| 의미 전달 | 게이트 01 / 06 판독기 — 자막 |
판을 통과하면 그 단계가 켜진다 |
캔버스는 position: sticky; height: 100dvh 로 화면에 머물고 섹션이 지나간다.
진행도는 섹션 높이가 아니라 (섹션 높이 − 뷰포트 높이)로 잰다.
섹션 높이로 나눴더니 캔버스가 사라진 뒤에도 진행이 남아 6단계 중 3단계에서 멈췄다.
그리고 기준 요소는 캔버스가 아니라 섹션이다 — sticky 캔버스는 자기 rect 가 변하지 않는다.
캔버스 위 텍스트 대비 실측 14.2:1(캔버스 불투명도 0.7).
패럴랙스가 세 번 실패한 경위
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 로 생성)
CSS 그라디언트 광원만으로는 기준면이 약해서, 굴절과 그레인이 실제로 들어 있는 이미지를 넣었다.
Codex CLI 의 내장 image_gen(gpt-image-2)을 ChatGPT 구독 인증으로 호출해 만들었다 — 스톡 이미지가 아니다.
섹션마다 다른 그림을 쓴다. 같은 배경을 전부에 깔면 그건 벽지고, 모션에 적용한 규칙("섹션마다 다른 말을 해야 한다")이 배경에서만 면제될 이유가 없다.
| 이미지 | 무엇 | 어디 | 그 섹션의 주장 |
|---|---|---|---|
bg-refraction |
겹친 유리판을 통과한 빛 | 재료 · 설치(flip) | 표면은 한 겹이 아니다 |
bg-split |
한 빔이 둘로 갈라진다 | 사례 | 같은 파이프라인, 다른 결과 |
bg-align |
정렬선 여럿 중 하나가 어긋나 끊겨 있다 | 프리플라이트 | 자기 결과물을 남의 것처럼 본다 |
bg-paper |
무광 인쇄용지, 섬유와 데클 엣지 | 조판(밝은 밴드) | 한글이 종이 위에 있어야 한다 |
격자를 쓰는 파이프라인·수치에는 사진을 넣지 않았다. 거기서는 격자가 그 섹션의 주장이다. 규칙·히어로도 그대로 뒀다 — 히어로에는 three.js 가 있고, 규칙 목록은 담백해야 한다.
프롬프트는 이 페이지의 토큰 값을 그대로 넣었다:
- 바탕
#0B0E12— 순흑도 네이비도 아니게 - 광원 둘만: 좌상단 딥 틸
#4FD6C4, 우하단 채도 낮은 파랑#3D7BD6. 보라·마젠타·바이올렛·무지개·따뜻한 색 금지 (슬롭 지문) - 프로스티드 글래스 판이 거의 옆에서 보이게 겹쳐, 가장자리만 얇게 빛나고 가운데는 거의 안 보이게
- 대비를 아주 낮고 고르게 — "이 이미지는 흰 본문 텍스트 뒤에 깔린다. 활자와 경쟁할 만큼 밝은 곳이 있으면 안 된다. 중앙 띠는 조용하고 어둡게, 밝기는 모서리 쪽에"
- 미세 필름 그레인, 밴딩 없이
- 금지: 사물·로고·글자·숫자·사람·렌즈 플레어·보케·딱딱한 도형·비네트
components/BgPhoto.astro 로 감쌌다. CSS background-image 가 아니라 <Image loading="lazy"> 를
절대배치한다 — background-image 로 깔면 항상 받고 최적화도 안 된다.
같은 이미지를 설치 섹션에서는 flip 으로 좌우 반전해 같은 그림으로 읽히지 않게 했다.
소스 WebP: refraction 37KB · split 14KB · align 11KB · paper 112KB
(종이만 큰 이유는 섬유 질감이 고주파라 압축이 안 붙기 때문이다)
빌드 결과 12개 파일 합계 276KB. 실제로는 뷰포트에 맞는 크기 하나씩만,
그것도 loading="lazy" 로 받는다.
절대배치 배경이 그리드 안에서 561px 로 잘렸다. .section 은 subgrid 이고
.section > * { grid-column: main; } 이 걸려 있는데, 절대배치 요소라도 그리드 영역이
잡히면 그 영역이 컨테이닝 블록이 된다. 그래서 배경이 화면 가운데 561px 띠로만 깔렸고,
overflow: visible 이라 위아래 14% 여유분이 이웃 섹션 위로 넘쳐 조판 섹션의 종이가
프리플라이트 하단에 밝은 띠로 나타났다.
.bg-photo { grid-column: full } + .has-bg { overflow: clip } 으로 고쳤다.
position: absolute 는 그리드에서 배치를 면제해주지 않는다.
codex 0.147.0 메모 — codex-image 스킬의 extract_imagegen.py 는 rollout JSONL 의
image_generation_call base64 를 찾는데, 0.147.0 은 다시 파일로 저장한다
(~/.codex/generated_images/<sid>/exec-*.png, 이벤트는 custom_tool_call + image_generation_end).
그래서 추출이 fail-loud 로 죽는다 — 이미지는 실제로 생성돼 있다.
그리고 $imagegen 은 프롬프트 맨 앞에 있어야 내장 도구가 발동한다.
그리드의 wide 트랙을 좁은 화면에서 닫는다
--wide-pad: clamp(0rem, -3.7rem + 15.2vw, 10rem)
고정 10rem 으로 두는 동안 모바일에서도 좌우 80px 씩 살아남아 390px 화면에서
본문이 x=112 부터 시작하고 폭이 278px 로 줄었다. 파이프라인 제목이 네 줄로 접혔다.
닫고 나서 모든 텍스트가 x=32 에서 시작하고 제목이 두 줄이 됐다.
같은 이유로 파이프라인의 제목·리드·브리프를 스파인과 한 컨테이너에 넣었다.
섹션 기본 그리드에 두면 제목 x=371 · 리드 x=435 · 스파인 x=143 으로 정렬선이 셋이 된다.
지금은 전부 x=183 이다.
디자인 SSOT
토큰이 없어서 깨져 있던 세 축을 채웠다.
- 웨이트 3종 —
400 / 500 / 600. 이전에는 코드 곳곳에500·560·600·620·660이 흩어져 6종이었고 셋은 토큰에 없는 값이었다. R1 모바일 실측은400이 832회, 나머지 12회다 - 컨트롤 치수 —
--control-h: 44px·--control-h-sm: 36px·--control-pad-x. 버튼처럼 생긴 것은 전부.btn+.btn-solid/.btn-quiet를 쓴다. 이전에는 두 버튼이 높이 36 vs 44, 패딩 16 vs 8, 테두리 1px vs 0, 웨이트 400 vs 500 이었다 --header-h— 떠 있는 헤더의 높이. 간격 토큰을clamp()로 바꾸는 순간 관계가 끊겨 h1 이 헤더 뒤로 들어갔다. 값이 아니라 관계를 토큰으로 둔다- 반응형 간격 —
--space-5~8과--gutter를clamp()로. 고정이면 모바일에서 섹션 상하 112px 이 그대로 들어가 화면의 26% 를 여백이 먹는다 - 스크롤바 —
scrollbar-color지정. 기본값이면 어두운 페이지 옆에 OS 색 밝은 막대가 선다
모바일
- 좌우 여백 24px → 32px (R1 모바일 실측값)
- 섹션 상하 112px → 56px
- 헤더에 섹션 앵커 4개 추가. 900px 미만에서는
<details>기반 메뉴로 접힌다 — JS 0바이트이고 열림 상태·Esc·포커스 순서·aria-expanded를 브라우저가 맞게 한다 - 390px 실렌더: 가로 스크롤 0, 대비 실패 0, 웨이트 3종, radius 2종
히어로 3D 의 형태 결정
- 판 15 × 6 (액자 비율 2.5:1 과 일치). 첫 판이 화면을 완전히 덮어 이미 첫 게이트 안에 들어와 있다
- 판이 화면보다 좁으면 통과하는 내내 좌우 테두리가 남아 세로 기둥 두 개로 보인다. 실제로 그렇게 보였다
- 판 높이가 시야보다 작으면 위아래 테두리가 액자 경계에 겹쳐 이중선이 된다
- 어긋남(sway)은
0.16 + 0.05i로 아주 작게. 키우면 '통과하는 문'이 아니라 쌓아둔 사각형이 된다. 중심축이 살아야 원근이 터널로 읽힌다 GATE_GAP6.4, 카메라 시작 z 7.2
다음 실행이 해야 할 일
- 배포 후 Lighthouse 로 첫 인터랙션·LCP·CLS 를 재고 "미측정" 을 채운다. 채우기 전까지 비워둔다
- 폰트를 CDN 의존에서 자체 호스팅으로 옮길지 판단한다 (현재 jsDelivr · Fontshare · Google Fonts 3곳)
- OG 이미지가 없다
- 섹션이 9개, 총 높이 11,197px 이다. 스크롤 보상이 충분한지 재검토할 여지가 있다
2026-08-20 · 2판. 0~6단계 전체 경로 재주행.
2026-08-21 · 쇼케이스 5종 확장 (총 6개 프리셋 완성).
- 무월 (muwol-coffee): quiet-commerce · 수치표 및 거래 원장 · 앰버 1색
- Pulsegate (pulsegate): dark-instrument · 28개 엣지 레이턴시 계기판 · 코발트 상태점
- 스튜디오 폼 (form-architecture): swiss-minimal · 12열 그리드 & 흑백 단면도 선화 · 무채색 98%
- 오버톤 사운드랩 (overtone-sound): anti-grid · 비대칭 FFT 파형 레이어 & 모노 타이포 · 일렉트릭 앰버 1색
- 한성활자연구소 (font-foundry): editorial-archival · 인터랙티브 조판 시험대 & 한지톤 파치먼트 · 인주 레드 1색
- 전체 쇼케이스 모자이크 그리드 6칸(1-lg, 2-md, 3-sm) 완성, 고해상도 스크린샷 10종 캡처 완료.
2026-08-21 · 쇼케이스 10종 전면 재주행 (목요일의 화원 제외)
0~6단계를 워크 단위로 다시 돌았다. 각 워크가 서로 다른 가상 클라이언트이므로 레퍼런스·컨셉·토큰을 페이지별로 새로 정의했다(국소 아님 — 토큰 체계 재정의 = 전체 경로).
감사에서 발견된 것 (재주행의 이유)
- 예전 10개 페이지는 전부 "계기판 흉내"였다: 영어 대문자 eyebrow, 티커 마퀴, 발광 상태점, 세로 GNB 레일, 카드+FIG 캡션, Math.random 가짜 샌드박스. 목요일의 화원만이 실제 편집 디자인이었다.
- dogfood 리서치 5종(aerospace·celadon·incense·optics·synapse)의 "실측값"이
날조였다 —
research/audit-references.md참고. spacex는 순흑+D-DIN, museum.go.kr는 라이트 테스트 등 전부 불일치. 이번 판에서 그 수치를 토큰 근거로 쓰지 않았고 각 페이지 tokens.css 머리글에 그 사실을 남겼다. 새로 실측한 리서치: dogfood-architecture(John Pawson 724px·동일 크기·1px 선), dogfood-overtone(carstennicolai 1152px·압축 스케일·obys 스태거), dogfood-foundry(klim 스페시멘 서사·레드 점묘).
페이지별 한 줄 (전부 tokens.css 머리글에 상세 기록)
| 워크 | 프리셋 | 이번 판의 결정 |
|---|---|---|
| 무월 | quiet-commerce | 우측 레일·eyebrow 6개 폐기. 가격 첫 화면(R1), 텍스트가 상품(R2), CTA=최암반전 블록 |
| Pulsegate | dark-instrument | 코발트는 잉크로만(배경 금지). 정적 24h 프레임, CSS scroll-draw 배선도, 단일 가격 행 |
| 향연 | quiet-luxury | 한지 SVG 그레인, 세로쓰기 캡션, 사진 없는 상품 목록, 본문 17px·1.9행간 |
| 비색의 침묵 | editorial-archival | 암실 3개의 방, 빙렬 풀블리드 판, 비색 액센트 1색 |
| 스튜디오 폼 | swiss-minimal | 무채색 100%·radius 0, 텍스트 인덱스(Pawson), 스티키 평면도 scroll-draw |
| 한성활자 | editorial-archival | 22rem 글리프+인주각, Lorem 0회, 시험대 슬라이더 3종(네이티브 input+output) |
| 오버톤 | anti-grid | 절차적 유사-FFT 캔버스(오디오 디코드 회피), 스태거 2열, 압축 스케일, 마퀴 1회 |
| Lumina | refraction/WebGL | three.js 커스텀 셰이더 프리즘(IOR·분산 유니폼), 본문은 캔버스와 세로 분리 |
| 궤도 | technical-schematic | 손그린 엔진 단면도(SVG 미러), 모든 수치에 측정조건, 보류 시험까지 공개 |
| Synapse | dark-instrument | 래스터는 SIM 명시, 미측정값 '—', 윤리 섹션을 콘텐츠로 |
프리플라이트 (기계 검증)
- 정적 grep: 슬롭 지문 0건,
100vh0건, scroll 리스너 0건, uppercase 0건. 토큰 밖 hex 7곳 → 전부 토큰화(각 페이지--*-bright/--accent-hover추가). - puppeteer DOM 감사 10페이지 × 3뷰포트(1440/390/320) = 30/30 통과: 가로스크롤 0, 깨진 이미지 0, 버튼·내비 줄바꿈 0, 텍스트×사진 겹침 0, 콘솔 에러 0.
- 걸려서 고친 것: pulsegate 320px 가로스크롤(인라인 code의 nowrap 최소폭 →
display:block+min-width:0), lumina 텍스트-캔버스 겹침(절대배치 폐기, 세로 분리 — "3D 위에 본문 금지" 원칙을 스스로 위반했었다). - 의도적 이탈 1건: 폼·pulsegate의
stroke-dashoffset스크롤 드로잉은 게이트 8(transform/opacity 외)에서 벗어난다. 선을 긋는 유일한 저비용 수단이며 레이아웃을 강제하지 않는 로컬 리페인트라 채택하고 여기에 기록한다.
이미지 파이프라인 (재사용 메모)
tools/gen-images.mjs→ codex 내장 image_gen(gpt-image-2), 프롬프트는 stdin. codex 배너가 stderr로 나오므로 세션 id 파싱은 e.stdout 포함 catch 필수. 병렬 실행 시 세션 회수 경합 —tools/collect-images.mjs(세션 dir 생성시각순=JOBS순)으로 회수.- 생성 22장 → webp(900
1600px, q82) 변환로 페이지당 28220KB. - 헤드리스 캡처에서 WebGL이 필요한 페이지는
--disable-gpu금지,--use-angle=swiftshader사용(capture-shots.mjswebgl: true).
다음 실행이 해야 할 일
- 비전 감사에서 남은 사소한 제안(차트 축 라벨 크기, 사진 해상도 아쉬움)은 기록만 남김 — 구조 결함 아님.
- showcase 페이지 언어판(en) 설명문을 새 디자인에 맞게 다듬을 여지.
2026-08-21 · 국소 — 모바일 메뉴 재작성 + 3건 오버플로 수정. 1′ 레퍼런스:
navbar.gallery → browserbase.com(항목 많은 사이트는 내비 마크업 자체를 복제해
분리한다; 우리는 5행이라 시트를 알약 전체 폭으로). <details> 결정 유지.
- 시트: 11rem 조각 상자 → 알약 전체 폭, 행 48px, mono 번호(01–05)·repo·언어 행, 요약 아이콘 버거→X 교체, 44px 터치 대상, 등장 translateY+opacity 180ms.
- 발견한 숨은 버그: 닫힌
<details>자식에 제작자display를 주면 UA 숨김을 덮어써 '보이지 않지만 레이아웃에 사는' 시트가 된다 — 320px +7px의 진짜 원인..nav-mobile:not([open]) .nav-sheet { display: none }안전장치 추가. - 행 클릭·바깥 클릭 닫힘 인라인 JS 몇 줄 추가(Esc·포커스는 여전히 브라우저 몫).
- ≤480px: 언어 버튼은 헤더에서 물러나고 메덥 행으로(320px 헤더 7px 초과 해소).
- Install 섹션:
@media의 순수1fr= minmax(auto,1fr) — nowrap 명령 min-content가 트랙을 밀어 390px +5px.minmax(0,1fr)로..t-path는overflow-wrap: anywhere. - 검증: / · /en/ · /showcase/ × 390/320 — 오버플로 0, 메뉴 열림/앵커 닫힘/바깥 닫힘
/행 48px/시트=알약 폭 전 통과.
2026-08-21· 국소 — 쇼케이스에서 내비 앵커가 죽는 버그. 내비 항목은 홈 섹션 앵커(#pipeline 등)인데 홈이 아닌 페이지에서는 그 페이지 안의 없는 앵커가 된다. Base 레이아웃에서 홈 판별 후/#…로 접두사(언어판별). 홈은 그대로#….2026-08-21· 국소 — 메뉴 시트 불투명화. .glass 의 잉크(white 4.5%)는 알약용 재질이지 시트용이 아니었다: 뒤 본문이 비쳐 겹쳐 보였다. 메뉴는 크롬 — 배경을 color-mix(surface 94%)로 채우고 rim/그림자만 유리 재질 유지.2026-08-21· 국소 — 쇼케이스를 카드+모달 구조로 재구성(사용자 지시). 1′ 레퍼런스 tailwindcss.com/showcase(카드 해부: 이미지+이름+한줄종류+태그, 3열, 카드 전체 클릭). taste-skill 4종 병행 적용. - 카드는 간단한 정보만: 스크린샷(호버 시 2번째 스크린샷 교체) + 이름 + 종류 + 프리셋 배지 + 컨셉 2줄 클램프. 리스크·지표는 카드에서 뺄샘 — 모달로.
- 그리드 비균일 유지: 첫 사례만 2열 폭(featured, 1440px에서 1100px), 나머지 3열(340px).
- 상세는 네이티브 +showModal — Esc·포커스 트랩·inert 를 브라우저가 처리
(메뉴와 같은 결정론). 배경 탭·닫기 버튼은 인라인 JS 몇 줄.
- 모달은 불투명 판(유리 금지 — 메뉴 시트와 같은 교훈), ::backdrop 만 블러. 큰 CTA "사이트 보기" = btn-solid + 둥근 화살 칸(cmd-copy 아이콘 어휘), 새 탭.
- 카드 전체 클릭은 절대배치
- 검증 3뷰포트(1440/390/320) 통과: 모달 열림·포커스 내부·Esc/배경/버튼 닫힘·
CTA _blank·오버플로 0·페이지에러 0. 닫기 버튼은 밝은 스크린샷 위 불투명 44px.
2026-08-21· 국소 — 프로모션 카피 전면 재작성(사용자 지시: 광고다운 포장) + 레이아웃 미조정. taste-skill 4종 병행. - 걸린 카피 결함: "없습니다는 제약"·"못합니다는 말" 어문 붕괴 2건, 존댓말/평서체 혼용 전역, EN 프리플라이트 3번이 design.md에서 이미 정정된 옛 이야기(Chrome 이 선언을 버린다)를 그대로 싣던 것, KO 프리플라이트 6항목만 싣고 note 는 "열세 건"이라 주장하던 것, 미사용 showcase 블록이 옛 자리표 시절 노트("표의 값은 비어 있습니다")를 설명하던 것.
- 카피 방침(파일 머리 주석으로 명문화): 본문 존댓말 통일, 제목은 단형 종결, 새 "—" 금지, 재지 않은 값은 미측정로. 포장은 혜택 문장으로 하되 수치는 그대로.
- KO 프리플라이트 13항목 완전 수록(EN 기준 역번역). EN backdrop-filter 항목 정정.
- 히어로: 리드를 "무엇인지+어떻게 다른지"로 재작성, 2차 링크 "사례 보기(/showcase)" 추가.
- 레이아웃: h1 text-wrap:balance + 하한 3rem→2.6rem(h2 33px 대 41.6px 위계 유지, 320px 4줄→2줄). EN 320px만 3줄(라틴 장문의 자연 꺾임, 데스크톱 2줄 기준 충족).
- 감사: / · /en/ × 1440/390/320 — 오버플로 0, h1 줄수 기준 충족, 링크 렌더 확인,
.entry 13항목 렌더 확인.
2026-08-22· 국소 — 관리 앱 형태의 사례 2종 추가(사용자 지시: 메뉴가 작동하고 모달 팝업이 있는, 학습관리 대시보드·수강신청 같은 앱). - gaon-lms 가온 학적부: 종이 학적부 톤(paper #f7f5ef, 흑연 사이드바 #24272b), 형광펜 밑줄 .hl(gradient 배경, 텍스트색 불변), 날인 .stamp. 뷰 3종(대시보드/수강생/ 과제·성적) 사이드 전환 + aria-current 이동, 검색·상태칩 필터, 학생 상세 모달 (메모 추가 폼), 채점 모달(저장 시 표 상태 '채점 대기→채점 완료' 갱신). 모달은 네이티브 — 쇼케이스 모달과 같은 결정론(Esc·포커스 트랩을 브라우저에).
- dure-enrollment 두레대학 수강신청: 남색 잉크 + 주홍 신청 도장. 과목 10개, 시간표 블록 grid-row=(start−9)×2+2(헤더 1행 오프셋), 겹침·정원 마감·학점 18 초과를 도장 누르기 전에 차단(openCourse 에서 warn + apply disabled), 결과는 인라인 status(aria-live)로. 취소는 블록 제거·학점 회수까지 되돌림.
- 320px 함정 재발: 모바일 미디어쿼리의 .layout 트랙이 순수 1fr(=minmax(auto,1fr)) 라 과목 행 min-content(426px)가 문서 전체를 밀었다(+122px). Install 섹션과 동일한 결함 — 축소 트랙도 minmax(0,1fr). 과목 행 안 .c-main/.course-list 의 암시적 auto 트랙도 함께 minmax(0,1fr)로(부제 '코드·시간·강의실'이 한 단어처럼 밀리지 않게).
- 시간표 .tt(최소폭 376px)는 .tt-scroll(overflow-x:auto)이 위젯 내부 가로 스크롤로 흡수 — 시간표의 표준 패턴이며 문서 오버플로가 아니다.
- -2 스크린샷을 '모달 열림 상태'로 캡처(2종 모두) — 카드 호버 교체 컷이 첫 화면과 동일한 나머지 사례와 달리, 모달이 핵심 기능인 이 두 사례는 교체 컷이 얘기해야 한다.
- 감사 25항목 통과: 뷰 전환/aria-current/검색 축소/칩 단일 선택/모달 open·닫힘(Esc· 버튼)/메모 추가(1→2)/채점 상태 갱신/겹침 차단(미시경제학↔생명과학, 월 14:00–14:30)/ 신청 블록 2개(월·수)/취소 회수/1440·390·320 오버플로 0.
- work.ts SSOT 등록: 항목 2개(size md, 6행 3+3). 지표의 JS 크기는 실측(7KB/10KB).
2026-08-22· 국소 — 관리 앱 2종에 화면 5개씩 추가(사용자 지시: 상세 페이지를 더 밀어붙이고 디자인도 개선). taste-skill 4종 병행. 연장 경로(토큰·컨셉 그대로). - 1′ 국소 레퍼런스(뷰 내비 축): docs.astro.build 사이드바 실측 — 폭 300px, 행 40px, 링크 16px, 그룹 헤더로 246개 링크를 정리. 결론: 우리는 뷰 8개라 220px 폭·기존 행 밀도 유지, 그룹 헤더 3개(학적/수업/학사) 만 도입. WebFetch 으로는 지오메트리를 얻을 수 없어 puppeteer 로 직접 실측(docs.stripe.com·docs.github.com 은 JS 렌더로 사이드바가 안 잡혀 두 번 실패 후 성공).
- gaon-lms 3→8 화면: 출석부(칸을 누르면 출석→지각→결석 순환, 주 전환, 반 전환, 휴학생은 ·), 학사 일정(2026-03 달력, 월요일 시작, 오늘=3/9 날인, 다가오는 일정), 상담 기록(memo 를 색인 카드로, 카드→기존 학생 모달 재사용), 자료실(배부 도장 토글 — 가짜 다운로드 링크 대신 상태만 기록), 학기 리포트(명단에서 실제 계산: 재원·평균 출석률·과제 제출률, 미채점은 '채점 중'으로 정직하게).
- dure-enrollment 1→6 화면: 앱바 아래 색인탭 내비(활성=남색 잉크 밑줄). 관심 과목 (모달 안 관심 남기기 → 여섯 화면이 같은 상태 공유), 신청 내역(여기서도 취소), 이수 현황(네 학기 기록 + 이번 학기 신청을 학점별 실시간 집계), 과목 총람(계열 소계 + 여석 임박 주홍), 공지·변경(게시판 행 문법). 빈 상태는 다음 행동 버튼과 함께(dashed 판 + '과목 찾기로 가기').
- 새 시각 언어: 장부 판(.sheet — 흰 종이에 먹줄 한 줄, dure 전 화면), 종이 그레인 (gaon body::after 고정 레이어, opacity 0.028, pointer-events:none), 표 행 hover, :active scale 0.92-0.995 촉감 피드백.
- 320px 함정 3연타(같은 결함): gaon 모바일 .app 순수 1fr → 사이드 min-content 721px 가 트랙을 밀었다(+401px). 고침: .app 축소 트랙 minmax(0,1fr) + .side min-width:0 + .side-nav max-width:100%(여덟 항목 가로 색인줄은 aside 안에서만 스크롤). 순수 1fr 은 이제 프로젝트 금지어 — 미디어쿼리 축소 트랙에도 minmax 를 붙인다.
- 감사 48항목 통과: 뷰 14개 전환·aria-current, 출석 순환·주·반 전환, 자료 필터·배부 토글, 달력 42칸·오늘·예정 7건, 상담 필터·카드→모달, 리포트 실계산(3명·91%·96%· 84.4점), 관심→목록 ★, 신청→내역·이수 +3 반영, 총람 소계, 겹침 차단 회귀, 내역에서 취소, 320·390·1440 오버플로 0.
- 스크린샷 재캡처(본 컷 + 모달 -2 컷), work.ts 지표 갱신(뷰 8/6종, 리포트 실계산,
색인탭, 빈 상태).
2026-08-22· 국소 — 모달 위치 결함 수정 + 표지 재질 개선(사용자 지시: 가운데도 안 있고 안 이쁘다). 두 앱의* { margin: 0 }리셋이<dialog>UA 스타일의margin: auto(가운데 정렬의 정체)를 지워 모달이 왼쪽 상단(left:0, top:10)에 붙어 있었다. 쇼케이스트 모달은 별도 스코프라 무사. - 수정: .modal 에
margin: auto명시(리셋 이긴 UA 값 되살리기). 앞으로 dialog 를 쓰는 페이지에서* { margin: 0 }리셋과 세트로 검사한다. - 표지 재질: 안쪽 먹줄(inset box-shadow ring 5px+1px — 스크롤해도 테두리 유지), 잉크빛 그림자(0 32px 64px -28px), ::backdrop 2px 블러+50% 딤, 등장 모션 translateY(14px)+scale(0.985) 240ms, 닫기 버튼 hover 채움(흑연/남색).
- 검증: 프로덕션 margin 상하 261.656/261.672 대칭(완전 중앙), -2 컷 재캡처.
2026-08-22· 국소 — 모달 재구성(사용자 지시: 빈약하고 안 이쁘다). "작은 상자에 항목 몇 개"를 넘어 모달을 판으로: 가온 학생 모달 760px 학적 카드(큰 숫자 3개, 실제 ATT 데이터에서 뽑은 2주 출석 스트립 10칸, 날짜열 상담 타임라인), 채점 모달 평균 실시간 계산(91.7→84.3, 형광펜 강조), 두레 강의계획 720px(큰 숫자 3개, 주간 자리 미니 격자 9–18시 비례 레일, 평가 비중 누적 막대 한 줄 — 같은 남색의 농도 3단). 모달 제목 step-3(28/30px)으로 상향. 검증: 세그먼트 63/189/378px=10/30/60% 정확, 비전 오탐(비율 안 맞는다는 지적)은 DOM 실측으로 뒤집음. 48항목 재감사 통과.2026-08-22· 국소 — "변한 게 없다"는 보고의 원인은 캐시. HTML 은 엣지가 안 담아(max-age=0) 항상 최신이지만, 정적 CSS 는 Cloudflare 존의 브라우저 캐시 TTL 4시간이 헤더를 14400 으로 덮어쓴다(cf-cache-status REVALIDATED 실측). 새 HTML + 옛 CSS 조합이 그려졌던 것. 대응: (1) 모든 워크 페이지의 CSS 링크를 main.css?v=2 로 버전 걸기 — 캐시를 안 통하는 새 URL. (2) public/_headers 로 /work/* 에 max-age=0 지정(존 설정이 우선하면 효과는 제한적). 운칙: /work 페이지 CSS 를 고치면 반드시 ?v= 숫자를 올린다. 근본 해법은 존의 Browser Cache TTL 을 'Respect Existing Headers' 로 바꾸는 것(대시보드 권한 필요).2026-08-22· 국소 — "탈락" 통보에 대한 재작업: 모달을 레퍼런스 실측 없이 머릿속에서 그린 것이 원인(스킬 1단계 위반). radix-ui.com 다이얼로그를 puppeteer 로 실측해 문법을 기준 삼음: 폭 500px·패딩 25px·제목 17px·컨트롤 35px 통일·반지름 6px· 완벽 중앙·헤어라인 링+부드러운 큰 그림자·수직 리듬 15-20px(shadcn/Stripe 데모는 리액트 이벤트가 안 잡혀 실패, Vercel 은 풀스크린이라 부적합, Framer 는 페이지 전환). 비전 조언도 같은 결론: 오스트ere한 원시 컴포넌트를 통째로 닮지 말고 구조 문법만 취하라. - 적용(레퍼런스 문법 + 종이 물성): 폭 760→620/600px, 제목 28→22/20px, 결산 숫자 축소, 컨트롤 높이 통일(36-38px), 그림자 0 25px 50px -12px, 백드롭 딤 0.55, 워터마크 7rem→4.5rem·투명도 5%, 수직 리듬 균일화(gap 20px).
- 검증: 로컬 비전 평가 8.5-9/10(예약 사항=워터마크 가독성 → 투명도 하향으로 반영), 48항목 회귀 통과, 프로덕션 v=4 확인(중앙 offX=0, 제목 22px, 스트립 10칸).
- 교훈(design.md 규칙으로 승격): 모달·새 컴포넌트를 만들 때는 반드시 실측
레퍼런스의 수치를 먼저 적는다. 느낌으로 폭·제목 크기를 정하지 않는다.
2026-08-22· 국소 — "변화량이 작다, 근본적으로 뜯어고쳐라" 통보: 스프레드(펼침 장부) 구조로 전면 재구성. 모달을 "작은 상자"가 아니라 앱 껍데기의 축소판으로 — 왼쪽 250px 표지판(가온=흑연 #24272b, 두레=남색 #1e3355)에 도장·이름·결산 행·물마크, 오른쪽 종이 내용판(스크롤은 여기만). Linear/Notion 피크 다이얼로그의 분할 문법. - 채점 모달은 단순 중앙판 유지(내용이 짧다). 모바일(≤680px)에서 표지판은 가로 띠로 접히고 통계는 물러난다.
- "안 바뀐다"의 두 번째 범인 발견: 로컬 3000번 서버가 옛 dist 를 서빙 중이었다 (netstat 에 LAN 다른 기기의 ESTABLISHED 확인 — 사용자가 옛 빌드를 보고 있었다). 서버를 public(소스 실시간)으로 재가동. 로컬 검증은 서빙 디렉터리부터 확인한다.
- 검증: 비전 9/10 두 건("Linear/Notion 피크와 대등"), 48항목 회귀 통과, 프로덕션 v=5(mcover 7곳) 확인.
2026-08-22 — 관리 앱 2종: shadcn 프로덕션 콘솔 문법(v6→v7)
- 왼쪽 상단 모달 버그 3범인 재확인:
* { margin: 0 }리셋이dialog의 UAmargin: auto(가운데 정렬)를 지운다..modal { margin: auto }없으면 모달은 (0,0)에 붙는다. - overflow-x: auto의 반대축 함정: 한 축만 auto/scroll로 지정하면 다른 축의 visible은 계산값 auto가 된다. 여기에
margin-bottom: -1px+보더 트릭이 더해지면 콘텐츠가 1px 넘어 세로 스크롤이 생긴다(Windows 비오버레이 스크롤바는 바로 보인다). 규칙:overflow-x: auto를 쓰면overflow-y: hidden을 명시하고, 음수 마진+보더 트릭 대신 border-box 높이로 해결한다. 탭 스트립은scrollbar-width: none까지(스와이프는 유지). - 찌꺼기 규칙 감사: 그리드 구조를 바꿀 때 이전 구조용 선택자(
.panel:last-child { grid-column: 1/-1 }등)가 남아 새 배치를 엉망으로 만든다. 레이아웃 규칙을 고치면 같은 선택자 범위의 형제 규칙을 전부 읽는다. - 빈칸은 데이터로 메운다: 대시보드 2열 그리드에서 우측 2행이 비었던 문제 — 패널을
grid-row: span 2로 늘리고 차트 아래 요일별 결산 표(출석/지각/결석/출석률+합계)를 넣어 해결. 장식이 아니라 프로덕션 정보(카드 부정보, 진행 중 배지, 미응시 경고)로 밀도를 올린다. - 캐시 운칙 준수: CSS 고칠 때마다
main.css?v=N증가 — 현재 v7.
2026-08-22 — LMS 필수 요소 조사 반영(v9)
- 웹 검색으로 LMS 필수 기능을 조사해 가온에 없던 다섯 가지를 추가했다: 알림 센터(탑바 종 + 드롭다운, 읽음 처리, 바깥 클릭 닫힘), 공지사항 뷰(핀 고정 · 카테고리 칩 · 읽은 사람 수 · 아코디언 본문), 수업 진도(과목별 차시 진행 바), 평가 유형(과제/퀴즈/시험 칩 + 자동 채점 상태), 출결 사유·학부모 알림톡 요약(병결/무단/보강/발송 수). 근거: 학원 LMS는 출결 알림(알림톡)·학부모 소통·보강이 일반 LMS와 구별되는 핵심 기능이라는 조사(웹헤즈, 출결톡 등).
- 폰트 라이선스 기록(typography.md §7 의무화): 두 앱 모두 본문 Pretendard Variable(SIL OFL, 웹 임베딩·서브셋 가능, 동적 서브셋 CDN) / 모노 JetBrains Mono(OFL). 폴백은 -apple-system·system-ui, 수치 위치엔 tabular-nums 적용.
- heredoc 잘림 재발: 긴 HTML 패치는 bash heredoc 대신 .mjs 패치 파일(Write 도구)로 쓴 뒤 실행한다 — 이미 두 번째 사고다.
2026-08-22 — 전 뷰 프로덕션 밀도(v10)
- "대시보드만 고쳤다"는 지적이 맞았다 — 뷰별 요약 스트립(statline) 컴포넌트를 만들어 아홉 뷰 전부에 같은 문법을 적용했다. 부분 개선은 완성이 아니다. 뷰 하나를 고치면 전 뷰를 같은 기준으로 훑는다.
- 뷰별 추가: 수강생(학년·보호자 열 + 실계산 요약 + 모달 보호자·알림톡 이력), 출석부(반·주 기준 실계산 요약 + 일별 합계 tfoot — 반 전환 시 재계산), 과제·성적(채점 진행 요약 + 과목 필터), 자료실(크기·조회 수 + 요약), 학사 일정(유형색 배지 4종 + 범례 + D-day + 월 요약), 상담(요약 + 예정 상담 3건 + 알림톡 상태), 리포트(학기 요약 카드 4장 실계산), 공지(분류 필터).
- 감사 스크립트도 제품이다: 검사 추가 시 셀렉터 다중 개수는 반드시 $$eval — $eval 은 첫 요소만 돌려줘 undefined.length 가 된다. 뷰 루프 순서가 바뀌면 '마지막 뷰' 전제 검사도 함께 바꾼다.
- 표에 열을 더하면 그 표가 .table-wrap 안에 있는지 다시 본다 — 자료실이 래퍼 밖이라 390px +104px 넘침이 재발했다.
2026-08-22 — 원론 조사 기반 전면 개정(v11): 타이포그래피·UX 방법론·한글 폰트
- 14개 웹검색·출처 110여 개를 정리한 research/design-principles-v11.md(105개 항목)를 기준으로 두 앱을 개정했다. 핵심 적용: 볼드 금지·수치 웨이트 원칙(700 제거, UA strong/th 볼드를 600/500으로 재정의, font-synthesis-weight: none), WCAG 대비(보조 텍스트 --faint #a1a1aa→#6e6e77, 2.6:1→5.05:1), 한글 자간 0 원칙(h2·h3의 -0.02em 제거, 큰 제목만 -0.01em), 행길이 제한(공지 본문 68ch), 터치 대상(coarse 포인터에서 출석 칸 40px).
- 폰트 전략(각주 한 문장): 가온 = Noto Serif KR 600(학적부 서류의 명조 결, 제목·표지만) + Pretendard Variable 본문. 두레 = IBM Plex Sans KR 본문(대학·기술 문서의 중성 목소리) + Noto Serif KR 600(공문·신문 결, 마스트헤드·제목). 모두 SIL OFL 웹임베딩 가능. 정적 웨이트 폰트(Plex)에 650을 요청하면 700으로 올라간다 — 가변 폰트에만 500 대역 중간값을 쓴다.
- 대형 사고: CSS 닫는 중괄호 하나를 잃어 규칙 절반이 죽은 채 배포될 뻔. 원인은 문자열 교체 패치가 닫는 } 를 삼킴. CSS 중첩 파싱 덕에 .tabs 하위 규칙은 살아 겉보기 정상이었고, .tabs 밖 규칙(표 헤더 등)만 죽어 UA th 볼드로 증상이 드러났다. 교훈: CSS를 고치면 중괄호 균형 검사를 감사에 넣는다 — 감사 도구에 brace-depth 체크 추가.
2026-08-22 — 모바일 눈 검수와 갸행 수습(v12/v13)
- "모바일은 눈으로 보라"는 지적이 맞았다 — 오버플로 수치만 보고 390px 실제 렌더를 본 적이 없었다. 390px 전 뷰 스크린샷 → 시각 검수 + Range.getClientRects 기반 줄바꿈 계측을 병행했다.
- 갸행 탐지기의 함정 두 개: ① 같은 줄의 strong/본문 서로 다른 폰트 메트릭이 top 을 1–2px 차이내 별도 줄로 오탐 → top 묶음에 ±5px 허용오차 + 높이 교차검증. ② 비전 검수는 작은 글자에서 환각(안 끊긴 시간을 끊겼다고 보고, 안 늘어난 카드를 늘어났다고 보고) → 비전은 끊김 '지점' 평가에만 쓰고, 여부는 계측으로 확정한다.
- 적은 수정: 라벨·본문 전역
word-break: keep-all; overflow-wrap: anywhere; text-wrap: pretty(한글은 어절에서만 끊고 외톨이 줄 방지), 마감 임박·예정 상담 행은 ≤680px 에서 세로 스택, 공지 행 ≤680px 1열(제목 전폭), tfoot 라벨 nowrap, 두레 과목행 부정보는 세그먼트(코드·교수·시간·강의실)별 nowrap<i>— 시간 값 "10:00–11:30" 이 중간에 끊히는 것을 원천 차단. - 검증 함정: 같은 세션에서 CSS 를 고치고 곧바로 찍으면 헤드리스가 옛 파일을 볼 수 있다 — 검증 직전 setCacheEnabled(false) 와 ?v= 범프를 같이 걸고 '실제 로드된 링크 버전'을 계측값에 함께 찍는다.
- 두레에 가온과 같은 뷰 밀도 기준 적용(관심 과목: 요약 스트립+계열 필터+학점 초과 사전 경고, 신청 내역·이수 현황·총람·공지: 실계산 요약 스트립) — "고치면 전체를 같은 기준으로" 재적용.
2026-08-22 — 5라운드 정밀 검수(v13/v14)
- R1 데스크톱 밀도: 두레 관심(402px)·이수(435px) 등 절반빈 뷰 발견 → 관심 빈 상태에 여석 임박 추천 3건, 이수 표에 구분별 진행 바+총 이수율 72% 바, 신청 내역에 학점 게이지+규칙 안내, 가온 리포트에 진행 차시 열.
- R2 극한 폭·상태: 320px 전 뷰 오버플로 0, 모바일 모달 1열·닫기 접근·뷰포트 내, 실제 Tab 키 포커스 링 전 요소 2px solid (스크립트 focus 는 focus-visible 미적용이 올바름 — 키보드로 재검), 신청→게이지 실시간 갱신.
- R3 플로우: 빈 검색 안내·특수문자 무결·필터+검색 조합·출석 순환 재계산·채점 갱신·메모→상담 동기화·관심 담기→추천 숨김·17학점+2 시도 → 신청 버튼 disabled (학점 초과 차단).
- R4 대비·타이포 전량 스캔: 전 요소 fg/bg 블렌드 대비 계산 → 28건 미달 전부 --muted-foreground #71717a(4.4:1) 탓 → #64646e 로 교정(5.2:1+), 달력 '오늘' 숫자 배경 #059669→#047857(5.4:1). 웨이트 5종(450·650·550 포함) → 400/500/600 3종으로 정규화. tabular 누락 0.
- R5 데이터 정합성: 대시보드 "재원 26명"↔명단 12명, 오늘의 수업 8/8·6/9·9/9↔과목별 재학 3·3·2·3명 등 세계관 모순 발견 → 12명 기준으로 숫자 체계 전면 재정합(헤드라인·차트·결산표·마감·과제 표·알림·출결 칩·공지 읽은 사람).
- 교훈: 데모의 숫자도 하나의 세계관을 이룬다 — 한 화면의 통계를 고치면 다른 화면의 같은 사실도 함께 고친다. 대비 스캐너(배경 블렌드 계산)와 정합 스윕은 감사 도구에 남길 가치가 있다.
2026-08-22 — 모바일 개행 재보고: 기기 글꼴 확대 시나리오(v14/v15)
- "모바일에서 개행 문제가 여전히 보인다"는 재보고. 프로덕션 실측(390/375/360)에선 h1 이 모두 1줄 — 문제는 기기 글꼴 확대(안드로이드 큰 글꼴 1.15~1.3×) 재현 시에 나타났다: h1 이 2줄로 부서지며 외톨이 글자가 생기고, rem 기반 여백까지 커져 제목 칸이 좁아진다.
- 해법 — 제목은 뷰포트 단위:
@media ≤680px { h1 { font-size: min(1.5rem, 6.4vw) } }. vw 는 사용자 글꼴 설정을 무시하므로 제목 크기가 고정되고, 본문은 사용자 설정을 존중해 커진다. 제목은 디스플레이 요소이므로 이 예외는 정당하다.text-wrap: balance를 h1(블록) 에 추가 — 예외시 균형 분할. flex 컨테이너(.n-t)의 balance 는 무의미해 제거. -webkit-text-size-adjust: 100%방어 추가(iOS 자동 인플레이션 차단).- 측정 교훈 3연타: ① flex 컨테이너 안 아이콘과 텍스트의 top 차이가 '가짜 줄'로 집계된다 — 텍스트 노드만 분리 측정할 것. ② 비전은 전사 기반으로만 믿을 것(추측 보고가 2번 환각). ③ page.evaluate 클로저 참조 함정 세 번째 반복 — 측정 함수는 문자열로 페이지에 주입한다.
- 검증: 폰트 스케일 1.0/1.15/1.3/1.45× 에서 h1 1줄 유지(양 앱), 공지 제목은 확대 시 어절 경계 2줄(자연스러움), 감사 ALL PASS.
2026-08-22 — 모바일 브랜드 세로 쌓임 결함(v15)
- "학적부 타이틀이 세로로 나온다"는 보고 — 표준 390px 에서도 재현: 모바일 가로 스트립에서
.brand가 flex-shrink 기본값(1)으로 side-nav 에 밀려 54px·5줄(글자 하나씩 세로)로 찌그러짐. 스트립 높이도 145px 로 부풀어 있었다. - 수정:
@media ≤900px { .brand { flex: none; white-space: nowrap } }— 브랜드는 고정 폭, 내비만 스크롤. 결과: 113px·1줄, 스트립 57px, 글꼴 확대 1.15/1.3× 에도 1줄 유지. - 교훈: 텍스트가 '세로로 쌓인다'는 보고는 컨테이너가 1글자 폭으로 수축했다는 뜻 — flex/grid 아이템의 shrink 기본값을 의심한다. 방어 검사에 '모든 텍스트 요소의 최소 자식 폭 ≥ 2em' 같은 수축 탐지를 넣을 만하다.
2026-08-22 — QA 게이트 구축(v16) — 6계층과 기계적 배포 강제
- 업계 조사(시각회귀·품질게이트·테스트피라미드·크로스브라우저·axe 57%·QA체크리스트)를 대조해 계획 보강 후 전 계층 구축: L0 정적(stylelint·html-validate) → L1 단위(calc.js 추출, 브라우저 없이 30 경계 테스트) → L2 불변식(기능 감사 61 + h1·수축·대비·axe·콘텐츠 정합·스케일×폭 행렬) → L3 시각 회귀(기준 30화면 pixelmatch, 0.000%) → L4 WebKit(Playwright, 8항목) → L5 탐색(키보드 완전 통과 + 여정 3개).
- 게이트가 잡은 실제 결함: CSS 중복 셀렉터 2건, aria-label 오용 26건, dl 안
구문 위반, role="img" 라벨 소실(내 정리의 부작용까지), 두레 시간표 role="grid" 구문 위반, 두레 #q 포커스 링 — 전부 사용자 보고 이전에 자동으로 발견.
- 배포는 이제
npm run verify && build && deploy체인으로만 — verify.mjs 실패(exit 1)면 배포 불가. 리포트(tools/verify-report.md)는 수치와 함께 자동 생성. - 하니스 교훈: ① 스코프 없는 선택자는 숨은 뷰의 같은 속성을 잡는다(대시보드 due-row도 data-assign 사용) — 선택자는 컨테이너 스코프 필수. ② 모달 닫힘 직후 포커스 강탈(비동기 복원) — 뷰 전환+타깃 포커스는 한 evaluate 로, 필요시 settle 후 재포커스. ③ UMD 가드는 module.exports 존재 검사가 아니라 module 존재 검사.
2026-08-22 — 스킬 내재화: 자율 검증 폐쇄 루프(v17)
- designpaca 스킬에 폐쇄 루프를 박았다: 핵심 규칙 6 "완료의 정의는 게이트 통과다", 5단계 기계 검사 4번째 "design-gate 실행 의무". references/audit-gate.md(사고-검사 매핑·SEO/meta 체크리스트·OS/브라우저 특성·하니스 규칙 7개) + tools/design-gate.mjs(범용 게이트, --init 스캐폴딩, 의존성 미비 SKIP) 동반.
- 도그푸드: 스킬 게이트가 두 앱에서 og:image·twitter:card 부재, 뷰마다 h1(9·6개) 를 잡음 → 탭 앱 표준 구조로 교정(브랜드=페이지 h1 sr-only, 뷰 제목=h2.v-h — 명시적 클래스, 선택자 추측 금지), OG 메타+og.png 추가, v17 배포(게이트 체인).
- 대형 사고와 자가 포착: 패치 스크립트의 튜플 실수(
([f,t])[1])가 'h'→'1'·'c'→'o' 문자 치환을 일으켜 main.css 2개·invariants.mjs 가 손상 — 시각 회귀(13.9% diff)가 즉시 포착, dist v16 빌드에서 복구. 교훈: ① 문자열 치환 패치는 사후 검증(변경 후 포함 여부 확인) 필수 — og 삽입도 조용히 실패한 뒤 재삽입했다. ② 패턴 매칭 대신 명시적 클래스(v-h)·명시적 앵커()를 쓴다. ③ 배포 아티팩트(dist)는 복구 원본이다. - 기준 화면 갱신 1회(h1→h2 의도 변경, 1~2px 라인박스 시프트) — 방법론대로 '검증된 의도 변경'으로 기록 후 갱신.
2026-08-22 — 시간표 시간 라벨 깨짐 수리(v18)
- 사용자 보고 "왼쪽 타임 라벨 깨짐" — 계측으로 확정: 라벨 top 간격이 22·194·43·22 불규칙, 일부 라벨 폭 78px(요일 열에 배치). 원인: span-2 셀이 섞인 그리드 자동 배치(auto-placement)의 취약성 — 시간 라벨(1행)과 셀(2행 스팬)이 교차하며 희소 배치 커서가 열1의 빈 행을 잡아 라벨이 엉뚱한 행·열로 흩어졌다. 세션 초반 의심 목록에 있던 "--rows 미사용 문제"의 실체이기도 하다.
- 수리: buildAxis 를 전 요소 명시적 격자 좌표(corner 1/1, day 1/(d+1), hour (h-9)*2+2행/1열, cell 행 span2/열 d+1)로 재작성 — 자동 배치 의존 제거. 검증: 9라벨 전부 1열(40px 균일), 간격 Δ43~44px 등차(2행×21.6px 설계 정합). 시각 diff 0.38%(라벨 재배치 = 의도) 확인 후 기준 갱신, 게이트 통과, v18 배포.
- 교훈: 혼합 span 의 그리드는 자동 배치를 믿지 않는다 — 좌표를 명시하라. 격자 레이아웃 검사에는 '라벨 등간격(Δ 균일)' 불변식을 넣을 가치가 있다(스킬 하니스 규칙 후보).
2026-08-22 — 프로덕션 마감(v18/v19): 상태 완결성 · 인쇄 · 단축키 · 브랜드 정합
디자인 스킬 13종 전량 병행 — designpaca 연장 경로(토큰·컨셉 승계) + redesign-existing-projects 감사 우선. taste 계열(안티슬롭·하이엔드·GSAP·미니멀·브루탈리스트·스티치)은 관리 콘솔에 적용되는 규칙만(상태 완결·대비·모션 문법·카드 절제·메타라벨 금지), 이미지젠 계열 4종은 구성·계층·가독성 기준으로 감사에 반영(관리 콘솔에 사진 소재는 부적합 — 생성 도구도 이 하니스에 없음).
- 브랜드 낡은 값 교정: theme-color 가 종이 시대 값(#F7F5EF/#FBFAF7), 파비콘이 v7 프리셋 전환 이전 문양(형광펜/남색)이었다. #fafafa + 현 브랜드 마크(가온=에메랄드 '가', 두레=벽돌색 신청)로 교체, og.png 재캡처.
- 상태 완결성: 가온 채점 입력에 범위 검증(GAON.validateScore — 0–100 밖이면 인라인 경고 + 저장 차단 + aria-invalid, 평균은 유효값만 계산). 두레 취소에 실행취소(상태줄 안에 되돌리기 버튼 — 재신청은 canEnroll 로 재심사, 사이에 다른 신청이 끼면 정당하게 막힘). 알림 센터 Esc 닫힘. 로딩 상태는 의도적으로 만들지 않았다 — 동기 인라인 데이터에 스켈레톤은 '저장을 흉내내지 않는다' 원칙 위반.
- 도메인 기능 — 인쇄: @media print 2종. 가온은 활성 뷰를 서류로(모달이 열려 있으면 body:has(dialog[open]) 로 앱을 숨기고 그 서류만), 두레는 A4 가로 시간표 + 강의계획서. print-color-adjust: exact 로 상태색 보존. 탑바/앱바에 인쇄 버튼.
- 키보드 단축키: 숫자키 뷰 전환(가온 1–9, 두레 1–6), / 검색 포커스(입력 요소 가드). kbd 물리 키 칩 안내 — 가온 사이드 하단, 두레 앱바(모바일 숨김).
- 모션 문법 토큰화: --ease-out(0.16,1,0.3,1)·--dur-1/2/3 한 곡선 세 지속시간. 뷰 전환 70ms 상승 — 시각회귀 스크린샷 대기(100ms) 안에 종료시켜 흔들림 없음. prefers-reduced-motion 전역 블랭킷(무한 루프·전환 정지, 기능은 유지).
- 토큰 위생: 지각 상태쌍(--late/--late-soft), --primary-hover, --accent-strong, --on-accent 를 tokens.css 로 이동 — main.css 의 상수색 제거. radius 를 5종 규모(--radius-xs/6/--radius-md/12/pill)로 정리, 난립하던 2/3/4/8/999px 리터럴 종식.
- 죽은 코드 제거: 가온 .page-head·.btn-primary/outline/ghost·.hl-count·.stamp(HTML 3곳·CSS), 두레 .modal-title/.modal-meta/.modal-inner/.facts(+모바일 쿼리 잔여), 두레 renderCreditsView 이중 계산 블록(첫 블록이 즉시 덮임), conflictsWith(canEnroll 로 흡수).
- calc.js 확장: GAON.validateScore, DURE.canEnroll(정원·겹침·학점 한 번에 판정 — 모달 경고 문장과 실행취소 재심사가 같은 순수 함수 공유). 단위 테스트 30→41건.
- 감사 도구 확장(도구도 제품): audit.mjs +24항목(채점 차단/해제·실행취소 복원·단축키 4건·인쇄 CSS 존재). exploratory 차터1 을 실제 완주로 고침 — type("88") 이 기존 값에 덧붙어 "9288"이 되던 조용한 테스트 결함(검증 추가로 처음 드러남; number 입력은 선택 덮어쓰기 불가 — 비우고 타이핑).
- 사고 2건, 게이트가 잡음: ①두레 320px +18px — 인쇄 버튼이 앱바 최소폭 합을 넘김 → ≤680px 액션 줄 2행 랩. ②가온 390px 6% 시각 diff — .side-kbd 기본 display:flex 가 파일 뒤쪽에 있어 앞쪽 900px 미디어쿼리의 display:none 을 캐스케이드로 이김 → 숨김 오버라이드는 기본 규칙 보다 뒤에 두어야 한다. 캐시 운칙 준수: main.css v18(가온)/v19(두레), calc.js?v=2.
- 쇼케이스 -2컷(모달 열림) 재캡처 — capture-work-shots.mjs 신설(CLI 크롬 --screenshot 은 모달을 못 열어 puppeteer 가 담당). 게이트 전 계층 통과(L0 2종·L1 41·L2 72+10·L3 30·L4 8·L5 14).
2026-08-22 — 스킬 내재화(v20): 이번 사고를 스킬 규칙으로 승격
프로덕션 마감 라운드에서 게이트가 사후에 잡은 결함 11종을 "스킬이 처음부터 조였다면 원천 차단됐을 규칙"으로 도출해 designpaca 스킬에 박았다(packages/skill — 하드게이트 13·14, 4-5 서류 기능 단계, preflight 상태 완결성 §4-1, 하니스 규칙 8~11, design-gate 신규 3검사). 이 앱들은 그 도그푸드 대상이다.
- 게이트가 수작업 스윕을 이겼다: 새 deadCss 검사가 내가 직접 정리하고도 놓친
.rep-stat-sub·:not(.page-head)잔여(모바일 쿼리)를, cssHygiene 이.bell-head button의border-radius: 4px리터럴을 각각 잡음. "눈 검사로는 절대 전부 못 잡는다"가 이 라운드의 입증이다. 세 건 정리. - 색 리터럴 잔여 전량 토큰화: 표지판 위 잉크(--panel-muted/faint/line/mark)·백드롭(--backdrop-dim)·인쇄 매체(--print-line/paper)·두레 평가 농도(--accent-tint-2/3) — main.css 색·radius 리터럴 0건(양 앱, 게이트 실측).
- 검증: 스킬 게이트 신규 검사 도그푸드 양방향 — 고의 결함 픽스처(죽은 클래스·토큰 밖 색·theme-color 불일치)는 3검사 정확히 FAIL, 실제 두 앱은 전 PASS. 리포 게이트 L0~L5 재통과. CSS v20(가온).
2026-08-23 — 모바일 앱 수준 UX(v21/v22): HIG·M3 표준 이행 · L6 시나리오 E2E · 실기기 검증
13종 디자인 스킬 재동원(designpaca 연장 + redesign 감사 우선). 조사를 먼저 — Apple HIG(탭바 56·시트·safe area), Material 3(바텀내비 35·48dp), NN/g(바텀 시트·터치타깃), CSS-Tricks(iOS 16px 확대)를 근거로 Nielsen 10 휴리스틱 감사로 위반 도출(백 키 앱 종료·중앙 모달 썸존 밖·safe area 무시·입력 확대 유발). 조사·계획: research/mobile-app-ux-v1.md.
- 앱 탐색 구조: 바텀 내비 5탭(가온 홈/수강생/출석부/성적/더보기, 두레 과목 찾기/관심/신청 내역/이수/더보기) + 더보기 바텀 시트(그룹 헤더 — 가온 학적/수업/학사 9항목, 두레 6항목). ≤900px 사이드내비/탭 전환.
- 하드웨어 뒤로가기 = 앱 내 히스토리: 뷰 전환 pushState, 다이얼로그도 같은 스택(suppressPop/popClosing 상호 무시 플래그, 더보기 선택은 pendingMoreSelect 로 back+push 접합). 백 키가 앱을 종료하지 않는다.
- safe area·입력·터치: viewport-fit=cover + env(safe-area-inset-*) max() 패딩, ≤680px 입력 16px(iOS 확대 방지 — maximum-scale=1 은 WCAG 위반으로 안 씀), coarse 44px 타깃, tap-highlight·overscroll·touch-action 위생.
- 바텀 시트: ≤680px 모달이 하단 시트로(그래버 36×4, 92dvh, sheet-up 모션), 인쇄에서 모바일 크롬 제거.
- PWA: manifest.webmanifest + sharp 생성 아이콘 192/512(가온=장부 문항 에메랄드, 두레=시간표 블록 벽돌) + apple-touch-icon·모바일 메타 2종.
- TDD RED→GREEN: tools/e2e.mjs 신설 — 앱 기반·320/390/768 매트릭스·터치타깃 231·대비 1,120·백 키·시트·더보기·강사 하루 장기 여정(S1 25단언)·정정기간 여정(S2 27). RED 1,742 단언 318 실패(신규 기능에 정확히 집중) → GREEN 1,762/0. verify 체인 L6 등록, 전 계층 재통과.
- 실기기 검증(안드로이드 에뮬레이터·실제 Chrome): adb reverse + uiautomator dump 텍스트 근거로 — 가온 렌더·수강생 탭·백 키→대시보드 복귀·더보기 시트·공지 선택·백 복귀, 두레 렌더·관심 탭·백 복귀. iOS 는 WebKit 엔진(L4)으로 대체(Windows 제약 문서화). 비전은 전환 중 프레임 오탐이 있어 '여부' 판정은 계측에만.
- 도그푸드가 잡은 사고: ①스킬 게이트 새 tapTargets 검사 개발 중 thresholds 얕은 병합 버그(구형 설정이 신규 기본값 지움, "≥undefinedpx") → 깊은 병합. ②죽은 선택자 검사가 이번 라운드 내가 만든
.btn-seal/.btn-star(가온에 없는 버튼)과 두레 포크 잔재 6종을 적발 → 삼위일체 제거, 세그먼트seg-${n}JS 조립은 무시 목록(seg-)으로 오탐 차단. ③두레.btn-seal은 생존(신청 버튼) — coarse 사이징 실수로 뺐다가 게이트 재실행으로 복원. CSS v22(양앱). - 스킬 내재화(v0.7 예정): references/mobile-app-ux.md 신규(5축 표·뒤로가기 전체 패턴·실기기 검증법·출처), audit-gate 7계층(L6 시나리오)·하니스 12~14(NaN 방어·fixed 판정·실기기 텍스트 근거), design-gate tapTargets 내장, SKILL.md 규칙 7 "OS 관습은 조사 없이 바꾸지 않는다".
2026-08-23 — 국소: 모바일 상단 바 수리(v23) — 스크롤 잔류·이중 헤더
사용자 보고 "상단 바가 페이지마다 왔다갔다하며 패딩이 없고 포지션도 이상하다". 계측으로 원인 둘로 확정:
- 탭 전환 스크롤 잔류 — setCurrentView 는 focus({preventScroll:true}) 였지만 이전 뷰의 window.scrollY 가 그대로 남아, 새 뷰가 짧으면 브라우저가 클램프(600→49)해 헤더가 반쯤 잘리고 길면 화면 중간에 떨어졌다. 뷰마다 크롬 위치가 제각각 = "왔다갔다". 수리: setCurrentView 마지막에 window.scrollTo(0,0) (양앱).
- 모바일 헤더 이중 스택 — ≤900에서 브랜드 줄(.side 43px)+크럼 탑바(52px)=95px 크롬이 쌓이고 둘 다 static 이라 스크롤하면 떠내려가며, safe-top 도 없었다. 수리: ≤900에서 .side 숨김(크럼이 브랜드명을 이미 담음 — 중복), .topbar 를 sticky top:0 + safe-top 패딩의 단일 헤더로(두레 앱바와 같은 문법). 데스크톱(>900) 무변경.
- 재발 방지: e2e.mjs M5 헤더·스크롤 스위트 10단언(헤더 sticky·상단 박착, 300px+ 스크롤에서 탭 전환 → scrollY=0·헤더 top=0). 시각 기준 갱신(390px 9화면 의도 변경), verify L0~L6 재통과. 스킬 mobile-app-ux.md 에 "뷰 전환은 스크롤을 리셋·헤더는 하나" 규칙 반영.
2026-08-23 — 국소: 모바일 헤더 재설계(v24) — 크럼이 아니라 브랜드다
v23 스크롤 리셋·단일 헤더에도 사용자 평가가 "여전히 깨져 보인다"였다. 에뮬레이터에서 라이브 URL로 재확인한 결과 구조(단일 sticky 바)는 맞았지만 시각적 무게가 틀렸다 — 작은 회색 크럼 텍스트("가온 학적부 / 대시보기")가 52px 바에 혼자 떠 있고 브랜드 마크는 사라진, 빈약한 헤더였다. 계측 전부 통과해도 보기에 빈약하면 실패다.
- 수리: 모바일 헤더를 브랜드(가 모노그램 타일+이름)+액션(인쇄·종) 한 줄로 — 두레 앱바와 같은 문법. 탑바에 브랜드를 이어받게(데스크톱은 사이드바 브랜드, 모바일은 탑바 브랜드 — CSS display 로 한쪽만 노출), 크럼은 모바일에서 숨김(화면 이름은 각 뷰 헤드와 활성 탭이 말한다). min-height 56px, sticky+safe-top 유지.
- 검증: 360/390/412px 오버플로 0·브랜드 노출·크럼/사이드 숨김 실측, 스크롤 800에서 탑바 top=0, 탭 전환 scrollY=0. 비전 검수 "확신 있는 앱 헤더·패딩 정상". 시각 기준 갱신(390px), verify L0~L6 재통과.
- 스킬 규칙 갱신: "크럼을 헤더로 내보내면 빈약해 보인다 — 모바일 헤더는 브랜드+액션, 계측이 통과해도 눈으로 봐야 한다".
2026-08-23 — 국소: 두레 왼쪽 인셋 리듬 수리(v23) — 제목이 가장자리에 붙어 보이던 사고
사용자 스크린샷 지적 "h2(2026학년도 1학기 과목)가 화면 왼쪽에 너무 붙는다". 스크린샷을 픽셀 판정으로 해부해 확정: 앱바 텍스트·검색·칩·카드는 전부 ~24px에서 시작하는데 h2만 16px — 8px 왼쪽으로 어긋난 큰 명조 제목이 '가장자리에 붙어 깨졌다'로 읽혔다. 원인: .layout 모바일 인셋이 var(--space-4)=16px인데 앱바는 space-5=24px — 같은 화면에 두 축이 공존.
- 수리:
.layout모바일 인셋 inline을 24px로 — 제목·카드·앱바가 같은 축. - 게이트 공백 발견: 기존 매트릭스는 오른쪽 '오버플로'만 검사하고 왼쪽 인셋의 최소값·정렬 정합은 검사하지 않았다(그래서 기준 갱신에 결함이 그대로 박혔다). e2e에 M6 왼쪽 인셋 스위트 신설(60단언): 전 뷰×320/390에서 텍스트 시작점 ≥12px + 헤더-제목 같은 축(±10px). RED 38 실패로 결함 재현 확인 후 수리로 GREEN.
- 측정 하자 2건 교정: display:none 요소의 rect=0이 잡히던 것(vis 필터), 전폭 컨테이너 보더박스가 아닌 '텍스트 시작점' 기준.
- 시각 기준 갱신(두레 390px 6화면), verify L0~L6 전 통과(E2E 1,805단언).
2026-08-23 — 신규 쇼케이스 3종 구축 (청온 케어 · 캐스크 아카이브 · 마테리아 스튜디오)
designpaca, design-taste-frontend, high-end-visual-design 원칙을 적용하여 신규 쇼케이스 사이트 3종 제작 및 쇼케이스 아카이브에 등록:
- 청온 케어 (
cheongon-care)- 프리셋:
ledger-console+swiss-minimal - 강조색: 에메랄드 틸 (
#0D9488) - 컨셉: 임상 수준의 6단계 위생 프로토콜과 투명한 정찰제 견적, 실시간 고객 신청-관리자 관제 대시보드 풀-루프 연동 및 32개 QC 체크리스트 모달.
- 프리셋:
- 캐스크 아카이브 (
cask-archive)- 프리셋:
editorial-archival+dark-instrument - 강조색: 싱글 몰트 앰버 골드 (
#D99B38) - 컨셉: Gordon & MacPhail, Signatory, SMWS 독립병입자 공식 수입 인증 보증서, 캐스크 넘버 원장 실시간 검색, 1:1 도슨트 테이스팅 예약 및 이벤트/인스타그램 연동.
- 프리셋:
- 마테리아 스튜디오 (
materia-interior)- 프리셋:
editorial+high-end-visual-design - 강조색: 브러시드 티타늄 브론즈 (
#9E8062) - 컨셉: 트래버틴과 훈증 오크의 물성, 자연광 Day ↔ 야간 조도 Night 앰비언스 실시간 전환, 더블 베젤 프로젝트 갤러리 및 프라이빗 공간 컨설팅 신청.
- 프리셋:
- 검증: Astro 빌드 완료, Stylelint 통과, HTML-Validate 통과, 전 계층 QA 게이트(L0~L6) 100% 통과.
2026-08-23 — 국소: 쇼케이스 상세 모달 및 카드 방문 액션 전면 재설계
사용자 피드백: "showcase 페이지의 상세 모달 팝업에 스크롤을 해야지 사이트 보기 버튼이 있어서 사람들이 사이트 들어가질 않는 문제" 및 "왼쪽 섬네일이 그냥 cover 형태로 꽉 차는 게 이쁘겠다".
- 문제 진단:
- 기존 단일 열 모달 레이아웃에서 거대한 스크린샷과 설명/리스크/지표 박스 아래에
.dialog-site링크가 묻혀 있어, 사용자가 모달을 열고 스크롤하지 않으면 사이트 진입 버튼의 존재를 알기 어려웠음.
- 기존 단일 열 모달 레이아웃에서 거대한 스크린샷과 설명/리스크/지표 박스 아래에
- 해결책 (2-Tier & Split Full-bleed Architecture):
- 데스크톱 (≥860px):
- 좌/우 2열 분할 스플릿 그리드(
dialog-layout). - 좌측(
dialog-media): 100% 풀블리드object-fit: cover스크린샷 뷰어 패널(여백·내부 테두리 제거로 미려하고 꽉 찬 인상). - 우측(
dialog-details): 상단 헤더 바로 아래에 눈에 띄는 고대비 주 CTA 블록(dialog-cta-block+.dialog-site-btn)을 배치하여 0px 스크롤(첫 시야)에서 100% 즉시 노출.
- 좌/우 2열 분할 스플릿 그리드(
- 모바일 (<860px):
- 상단 풀블리드 섬네일 커버 + 헤더 직하단 CTA 블록.
- 하단 고정 스티키 액션 독(
dialog-sticky-dock+.dialog-dock-btn)을 적용하여 스크롤 중에도 엄지 영역에 상시 '사이트 보기' 버튼 고정.
- 카드 그리드 즉시 진입:
- 카드 썸네일 호버 시 우측 하단에
.card-quick-visit알약 버튼 노출. - 타이틀 행에
.card-direct-link아이콘 버튼을 배치하여 모달을 열지 않고도 바로 새 탭에서 데모를 체험할 수 있도록 다이렉트 액션 강화(z-index: 4).
- 카드 썸네일 호버 시 우측 하단에
- 데스크톱 (≥860px):
- 글로벌 리셋 보강:
base.css에 글로벌 미디어 리셋(img, picture, video, canvas, svg { max-width: 100%; display: block; }) 및html { overflow-x: hidden; }안전장치 추가.
- 자동화 검증:
tools/test-showcase-dialog.mjs(1440px / 768px / 390px / 320px 4개 뷰포트 E2E): 데스크톱 0px 스크롤 CTA 노출 확인, 좌측 cover 핏 실측, 모바일 스티키 독 활성화 확인, 모달 닫기/Esc/배경 클릭/카드 액션 등 17개 단언 100% PASS.
2026-08-24 — 신규 쇼케이스 3종 (RFS · 트웬티온스 · 새벽청과) + 게이트 두 겹 신설
각 워크는 서로 다른 클라이언트이므로 0~6단계를 워크 단위로 따로 돌았다(토큰 체계 신규 정의 = 전체 경로).
세 워크 모두 라이트/다크 반전을 토큰 규칙으로 정의해 하드 게이트 4를 통과시켰다 —
.band-light / .band-dark 가 --band-* 를 재정의하고, 컴포넌트는 --band-* 만 본다.
그래서 반전은 섹션마다의 기분이 아니라 두 클래스가 만드는 규칙이다.
워크별 한 줄 (상세는 각 tokens.css 머리글에 실측치까지 기록)
| 워크 | 프리셋 | 레퍼런스 (실측) | 컨셉 · 감수한 리스크 |
|---|---|---|---|
RFS rfs-cfd |
darkroom-spec (신규) |
R1 luminary.ai(배경 #2A2C2F·h1 72/600·radius 0 1898회·히어로 영상이 첫 화면 100%) · R2 teenage.engineering EP-133(#F9FAF9·강조 #FF5000 단 10회·밝은 판에 다크 블록 18회) · 보조 simscale.com(1192↔1425 교대·h1 49.78/400) | 암실과 사양서. 유동이 있는 곳은 어둡고 읽는 곳은 밝다. 리스크: 히어로에서 브라우저가 실제로 Navier–Stokes 를 푼다 |
트웬티온스 twentyoz-corp |
field-record (신규) |
R1 samsara.com(#FFFFFF·FK Grotesk 2패밀리·최대÷본문 5.0·radius 0 2399회) · R2 lars-mueller-publishers.com(본문 20px·h1 34px·최대÷본문 1.7·radius 0 1709회) · R3=현행 twentyoz.kr 감사 | 네 개의 현장. 리스크: 첫 화면에 회사 이름·슬로건을 두지 않고 현장 선택기를 둔다 |
새벽청과 saebyeok-cheonggwa |
market-board (신규) |
R1 natoora.com(#121212·본문 16px 224회·최대÷본문 4.5·히어로 이미지 1313×900) · R2 phillips.com(무채색·h1 28px/400·날짜와 상태가 제목보다 앞) · R3 제철 12개월 띠 | 오늘 새벽 두 시의 값. 리스크: 상품 사진이 아니라 시세판이 히어로다 |
두 레퍼런스의 합성을 기록해 둔다 — 트웬티온스는 R2(본문 20px)로 본문을 키우고 R1(최대÷본문 5.0)로 스케일을 벌렸다. 한쪽만 따르면 평평하거나 본문이 작아진다.
RFS — 히어로가 실제로 유체를 푼다
scripts/thermal-field.js, 순수 WebGL2. three.js 를 쓰지 않았다(예산 절약이 아니라
셰이더 플레인 몇 장이면 되는 일에 씬 그래프가 필요 없어서다).
- 패스: 이류(semi-Lagrangian) → 온도 소스 → 부력·설비 힘 → 발산 → 압력 Jacobi → 투영 → 파티클 전진
- 격자 224×126 / 파티클 26,000 / Jacobi 22회. 좁은 화면·저코어면 128×72 / 9,000 / 14회, DPR 1.25
prefers-reduced-motion이면 아예 시작하지 않는다. 그 자리에 RFS 실물 파티클 렌더가 남는다 — 폴백이 아니라 결과물이다- IntersectionObserver 로 화면 밖이면 정지, 탭이 숨어도 정지
세 번 고쳤고 원인은 전부 단위계였다.
- 파티클이 0.2초 만에 사라졌다.
dt를 "프레임 단위"로 넘기면서 수명 감쇠를age -= dt * 0.115로 걸었다. dt≈0.9 라 프레임당 0.1씩 줄어 10프레임이면 소멸이다. dt 를 초로 못박고 속도의 단위를 "초당 격자 셀"로 통일해 해결했다. 감쇠도 프레임 상수(0.9965)에서pow(base, dt*60)으로 바꿨다 — 주사율이 물리를 바꾸면 안 된다. - 입자가 실 한 가닥으로 뭉쳤다. 전부 급기구에서만 재생성해서 같은 경로를 따라갔다. 방이 아니라 소용돌이 그림이 됐다. 절반만 급기구에서, 나머지는 방 안 임의 위치에서 되살린다.
- 핫 아일/콜드 아일이 안 보였다. 랙이 위로 뿜게 했더니 냉기와 열기가 랙을 사이에 두고 갈라지지 않았다. 전면 흡입 → 배면 토출로 바꿔 이 업계의 기본 그림이 화면에 나오게 했다.
수치는 전부 RFS 개발 저장소 README 출처다(파티클 100,000 / GPU 커널 53+ / 20B SoA / 테스트 2,000+ / Unity 6.4 · URP 17.2 / GTX 1660~RTX 3060). 정확도 지표는 싣지 않았다 — 사내 검증 문서의 등급은 고객사 대상 내부 자료이고 현재 통과 상태가 아니다. 성능이 아니라 정직함이 이 페이지의 자산이므로, 없는 것은 없다고 두었다.
트웬티온스 — 리디자인 감사가 R3 를 대신했다
현행 twentyoz.kr 을 브라우저로 열어 computed style 을 실측했다. 여기서 나온 것이 리디자인의 근거 전부다.
- 대비 실패 172개 요소 —
rgb(102,102,102)가 배경rgb(2,2,2)위 3.61:1, 그것도 9~11px 로 284곳 - 한글 폰트 미지정 —
font-family가 Inter 뿐이라 한국 회사의 한국어 사이트에서 한글이 시스템 기본으로 떨어진다 - h1 174.86px 에
letter-spacing: -10.49px= −0.06em. 한글 상한(−0.02em)의 세 배 - 배경
#020202— 순흑에 가깝다 - 'Digital Genetics' 은유가 전면에 있다(CULTIVATING · DROPS · Sativa/Indica · Germination · Wholesale Desk · ACQUIRE GENETICS). SK하이닉스·LG디스플레이·한화솔루션·GS칼텍스에 산업용 IoT 를 파는 회사의 어휘로는 사업을 가린다
유지한 자산: radius 0(742회로 이미 통일), 무채색 기조, 모노스페이스. 형태 어휘는 옳았다.
현장 전환은 라디오 그룹 + :checked ~ 로 JS 0바이트다. 탭 흉내를 내지 않고 라디오를
쓴 이유는 좌우 화살표 키가 그냥 먹기 때문이다 — 직접 만든 탭은 그 키를 다시 구현해야 한다.
파트너 19곳을 현장별로 나누지 않았다. 원본 사이트가 도메인을 표기하지 않으므로 내가 나누면 그건 추측이다. 프로젝트 12건은 이름·연도·기술 스택이 원본에 있으므로 그대로 옮겼다 (공개 카탈로그 49건 중 12건만 노출돼 있었다 — 그 사실도 페이지에 적었다).
새벽청과 — 값이 바뀐다는 사실을 감추지 않는다
커머스가 보통 감추는 것(시세 변동)을 히어로의 주인공으로 삼았다. 그래서 상품 사진은 아래로 내렸고, 첫 화면은 표다.
카피의 사실 근거를 전부 조사해서 넣었다: 가락시장 과일 경매는 새벽 2시~아침 8시이고 사과·배는 08:30 이다 · 유통 경로는 출하 → 도매법인 수집 → 경매 → 중도매인 낙찰 → 소매 · 낙찰가를 경락가격이라 부른다 · 등급은 특·상·보통이고 당도 표시는 특·상 등급에만 허용된다 · 표시 가능 품목은 8+5 종이라 무화과는 들어 있지 않다 · 선과기는 드럼식(지름)과 중량식(무게) · 거래단위는 사과 2·5·7.5·10kg, 포도 2·3·4·5kg.
무화과의 당도 칸을 — 로 두고 "값이 없다는 뜻이 아니라 표시가 허용되지 않는 품목"이라고
적은 것이 이 페이지에서 가장 마음에 드는 한 줄이다. 규정을 아는 사람만 쓸 수 있는 문장이다.
하드 게이트 11 대응: 표의 숫자는 가상 상호의 예시값이므로, 판 아래와 푸터 두 곳에 "이 쇼케이스를 위한 예시" 라고 명시했다. 주소·전화·사업자 정보는 지어내지 않고 비웠다.
등락은 색으로만 말하지 않는다. ▲ ▼ 보합 기호가 먼저고 색은 거들지 않는다 —
색맹 사용자에게 빨강/파랑만 남는 판은 판이 아니다.
이미지 4장은 codex 내장 image_gen(gpt-image-2)으로 생성했다. 스톡이 아니다.
프롬프트에 이 페이지의 토큰값(#FBFAF7 · #16181A · #1F3F6B)을 그대로 넣었다.
합계 341KB(webp).
게이트 두 겹을 새로 심었다
기존 체인(stylelint · html-validate · L1~L6)이 구조적으로 못 보던 자리가 있었다.
1) tools/integrity.mjs — L0-무결성 (verify 체인에 편입)
stylelint 도 html-validate 도 "var(--x) 를 썼는데 --x 가 어디에도 없다"를 못 잡는다.
미정의 변수는 선언 전체를 무효로 만드는데 콘솔 에러가 없어서 조용히 살아남는다.
심자마자 직전 릴리스(v0.9.1)의 실제 결함이 나왔다:
| 발견 | 내용 |
|---|---|
| 미정의 CSS 변수 | cask-archive 25건 · cheongon-care 20건 · materia-interior 19건. --dur-fast · --radius-xs 가 정의된 적이 없어 transition 선언이 통째로 무효였다(호버 전환이 전부 즉시 점프) |
| 한글 조판 누락 | lumina-optics · pulsegate · synapse-bci 가 word-break: keep-all 없음, 그리고 IBM Plex Sans 만 지정해 한글 폰트 미지정 |
| 슬롭 지문 | text-transform: uppercase 로 만든 영문 대문자 eyebrow 19건(슬롭 검출률 2위 항목) |
| 하드 게이트 9 | materia-interior/hannam-duplex.html 의 100vh |
| 죽은 파일 | 그 리다이렉트 스텁은 어디서도 참조되지 않으면서 인라인 hex(게이트 1)까지 갖고 있었다 |
전부 고쳤다. --dur-fast → --dur-quick, --radius-xs → --radius 로 기존 토큰에 매핑했다 —
새 토큰을 추가하면 radius 어휘가 4종이 되어 하드 게이트 3에 걸린다.
대문자 eyebrow 는 한글 문장으로 바꾸고 font-mono 도 본문 서체로 되돌렸다(한글에 모노는 폴백된다).
2) tools/showcase-audit.mjs — 실렌더 감사 (320/390/768/1440)
정적 grep 이 못 보는 것을 실제 렌더에서 잰다: 가로 스크롤 · 뷰포트 초과 · 컨트롤 줄바꿈 ·
텍스트 대비 AA · 왼쪽 인셋 최소값 · h1 개수 · 히어로 헤드라인 줄 수 · 깨진 이미지 · 콘솔 에러.
html-validate 대상도 두 페이지에서 public/work/*/index.html 전체로 넓혔다 —
넓히자 기존 페이지에서 11건이 나왔다(라벨 하나가 컨트롤 둘에 걸림 5건,
dl > div > div > dt 4건, div 에 붙은 aria-label 2건). 전부 수리.
측정기가 두 번 거짓말했고 둘 다 고쳤다.
.sr-only를 대비 판정에 넣어 34건을 실패로 잡았다. 눈에 보이지 않는 텍스트다.clip-path+ 2px 이하 상자를 sr-only 로 판정해 제외한다overflow-x: auto상자 안의 넓은 표(제철 띠)를 오버플로로 잡았다. 그 상자가 실제로 스크롤될 때만 면제하도록 조상을 걷어 올려 확인한다
두 번 다 페이지가 아니라 도구가 틀렸다. 고치기 전의 통과·실패는 의미가 없었다.
의도적으로 하지 않은 것
- RFS 에 정확도 지표를 싣지 않았다 — 사내 검증 등급은 공개할 성질이 아니고 현재 통과 상태도 아니다. 없는 것은 없다고 둔다
- RFS 히어로를 3D 로 만들지 않았다 — 제품이 3D 인 것과 홍보 페이지가 3D 여야 하는 것은 다른 문제다. 2D 축소판이라고 캡션에 적었다
- 트웬티온스 파트너를 현장별로 배정하지 않았다 — 원본에 없는 정보다
- 새벽청과에 주소·전화·사업자번호를 넣지 않았다 — 가상 상호에 그럴듯한 연락처를 적으면 하드 게이트 11 이다
- 세 페이지 모두 마퀴·펄스 상태점·요금제 3열 카드를 쓰지 않았다
dark-instrument를 세 번째로 쓰지 않았다 — 저장소에 이미 둘(Pulsegate·Synapse) 있다. 같은 프리셋의 셋째는 결정이 아니라 카테고리 평균이다
다음 실행이 해야 할 일
tools/verify.mjs의 L2~L6 은 여전히 관리 앱 2종(가온·두레) 전용이다. 정적 쇼케이스 17종은 L0 두 겹과showcase-audit만 돈다 — 시각 회귀 기준을 쇼케이스로 넓힐 여지가 있다- LCP·CLS 는 세 페이지 모두 미측정이다. 배포 후 모바일 프로파일 Lighthouse 로 재야 한다
pulsegate·synapse-bci·lumina-optics의 한글 폰트를 스택에만 추가했다. 실제 조판(자간·행간)은 IBM Plex Sans 기준으로 잡혀 있어 한글 눈 검수가 남아 있다
성능 — 실측과 예산 이탈 한 건
performance.getEntriesByType('resource') 로 첫 화면 전송량을 실측했다(RFS 기준).
| 항목 | 예산(데모) | 실측 | 판정 |
|---|---|---|---|
| 히어로까지 JS (gzip) | 400KB | 24KB | 통과 — three.js 를 쓰지 않았다 |
| WebGL 추가분 | +600KB | 0KB | 통과 — 순수 WebGL2 라 라이브러리가 없다 |
| CSS | — | 41KB | — |
| 이미지 | — | 107KB | — |
| 폰트 | 200KB | 530KB | 초과 |
| 총 전송량 | 2MB | 704KB | 통과 |
| 리소스 수 | — | 27 | — |
| LCP · CLS | 3.5초 / 0.1 | 미측정 | 배포 후 Lighthouse |
폰트 예산 초과를 그대로 적는다. 원인은 이펙트가 아니라 한글 웹폰트 자체다.
Pretendard dynamic-subset 은 유니코드 범위별로 파일을 쪼개고 브라우저는 실제로 쓰인
범위만 받는데, 한국어 본문은 받는 범위가 많다. 비교 실측: 기존 cheongon-care 는
650KB / 25개 파일(jsdelivr 619KB + gstatic 31KB)로 더 크다. 이건 이번 판이
악화시킨 값이 아니라 저장소 공통 baseline 이다.
여기서 할 수 있는 선택은 셋이었다.
(a) 한글 폰트를 빼고 시스템 기본으로 — antipatterns.md 가 금지한다.
(b) 서브셋을 직접 만들어 호스팅 — 페이지에 실제로 쓰인 글자만 담으면 수십 KB로 떨어진다.
(c) 초과를 기록하고 넘어간다.
이번 판은 (c)다. 세 페이지가 정적 HTML 이고 CDN 캐시를 공유하므로 두 번째 페이지부터는
0KB 이며, (b)는 쇼케이스마다 빌드 단계를 새로 붙여야 해서 이 작업의 범위를 넘는다.
다만 tokens.md §5 의 폰트 예산 200KB 는 한글 프로젝트에서 달성 불가능한 값이다 —
한글 전용 예산선을 따로 두거나 (b)를 파이프라인에 넣는 것이 다음 판의 일이다.
기존 16종에 남아 있는 것 (이번에 고치지 않았다)
showcase-audit.mjs 를 19종 전체에 돌린 결과 신규 3종은 12/12 통과, 기존 16종에서 70건이
나왔다. 이번 작업의 범위(신규 3종)를 넘으므로 고치지 않고 목록만 남긴다.
- 대비 미달 —
overtone-sound의.work-meta13px 모노 3.57:1,synapse-bci의 1112px 캡션 3.843.98:1,thursday-flowers의.visit-mark1.33:1(크림 #DDD7CA 글자가 크림 #F8F6F0 배경 위에 있다. 장식용 세로쓰기 표시지만 사실상 안 보인다) - 히어로 헤드라인 줄 수 초과 —
pulsegate320px 에서 4줄 / 1440px 에서 3줄,synapse-bci3줄 (카운트 규칙 상한 2줄)
전부 node tools/showcase-audit.mjs <slug> 로 재현된다.
TWENTYOZ 쇼케이스 — Precision Spatial (2026-08-24)
제품·카피 기준
- 정보 구조와 사실 카피의 단일 기준은
https://twentyoz.kr/의 회사·제품·뉴스 공개 자료다. - 메인 순서는
현장 → 운영 원칙 → 제품 → 프로젝트 기록 → 회사 → 문의다. - 제품 상세는
products.html한 페이지에서 VIVEN, EduBill, XD Hub, XD Works를 기능·기술·사양·운영 순서로 공개한다. - 공개되지 않은 파트너 역할, 성과 수치, 채용 상태를 추정하지 않는다. 모든 한국어 인터페이스 카피는 존댓말 또는 공식 명사형을 사용한다.
시각·3D 규칙
- 콘셉트는 정밀한 공간 계기다. 평면 DOM은 직각·선·여백·타이포가 소유하고, 원형과 곡면은 3D 코어만 소유한다.
- 외부 하드서피스 모델은 Javier Martín Hidalgo의
Scifi Drone 1.1을 CC BY 4.0 조건으로 사용하며, 화면 푸터와 로컬 라이선스 파일에 저작자·원본·라이선스를 표시한다. - 중심 모델은 전면
z=0.46, 호박색 신호 링은z=-0.48, 청록색 스펙트럼 링은 더 크고 얇게z=-0.64에 둔다. 두 링은 코어 뒤에서 깊이 가림되어 형태가 겹쳐 보이지 않아야 한다. - 셰이더는 필드 셸, 제한적 bloom, 미세 색수차·비네트·그레인에만 사용한다. 모델 재질을 덮는 임의
onBeforeCompile은 금지한다. - WebGL 실패·감소 모션·문서 비가시 상태에서는 정적 테마별 WebP와 완성된 DOM이 그대로 남아야 한다.
검증 기준
npm run test:e2e:twentyoz: 메인 320/390/768/1440, 네 현장 상호작용, WebGL 모델·셰이더·이중 링, 라이트/다크 axe, 폴백, 제품 상세, 성능·런타임을 검사한다.npm run test:design:gate: 메인 쇼케이스의 죽은 CSS·토큰·메타·오버플로·타입·대비·터치·인셋을 검사한다.npm run test:design:products: 공통 CSS의 죽은 선택자는 메인 게이트에 위임하고 제품 상세의 실렌더 게이트를 검사한다.npm run verify: 위 트웬티온스 전용 게이트와 저장소 전체 L0~L6를 한 번에 실행한다.- 3D 번들 예산은 gzip 200KB 이하이며 현재 기준은 166,225B다. GLB는 697,076B이며 로컬 폰트·폴백 이미지를 포함한 예산은 전용 E2E에서 하드 실패한다.
- 2026-08-25 국소 조정: 3D 화면 중심은 모바일 50%, 태블릿 74%, 소형 PC 64%, 와이드 PC 60%를 기준으로 하며, 중복 현장 상태는 보조기술에만 알리고 3D 모션 제어는 히어로 CTA 행에 둔다.
RFS CFD · 새벽청과 연장 업그레이드 (2026-08-25)
유지한 방향
- 신규 콘셉트로 갈아엎지 않고 기존의 검증된 대비를 연장한다. RFS는 암실과 사양서, 새벽청과는 오늘 새벽 두 시의 값을 계속 기준으로 삼는다.
- RFS의 실제 시뮬레이션 렌더와 새벽청과의 실물 과일 사진을 유지했다. 생성 이미지로 제품 증거나 산지 품질을 꾸미면 실제 출력·실물처럼 오해될 수 있으므로 이 판에서는 이미지 생성 도구를 쓰지 않았다.
- 장식 면적을 늘리지 않고 사용자가 정보를 읽은 다음 내릴 수 있는 판단과 행동을 추가했다.
RFS — 조작에서 해석으로
- 히어로에
기준 운전 · 랙 과부하 · 급기 저하프리셋을 두고 서버랙 부하와 FWU 급기를 실제 슬라이더 값으로 함께 바꾼다. - 부하와 급기의 차이를 읽어
현재 상태 · 먼저 볼 곳 · 다음 조작을 한국어로 해석한다. 정확도나 온도 수치는 새로 만들지 않는다. - 상면·측면 실물 렌더를 한 뷰어의 전환 상태로 합쳐 두 이미지가 세로로 쌓이며 생기던 공백을 없앴다.
- 감소 모션이나 WebGL 실패에서도 프리셋·해석·렌더 전환은 작동하고, 유동 화면만 정적으로 남는다.
새벽청과 — 시세에서 발주 메모로
- 경매판의 여섯 품목을 선택 가능한 로트로 바꾸고 산지·등급·당도·규격·낙찰가·전일 대비를 하나의 판독기에 동기화한다.
오늘 경매는 하드코딩 날짜가 아니라 한국 표준시의 실제 날짜로 표시한다.- 제철 띠에 이전 달·다음 달 탐색과 해당 달의 품목 목록을 붙였다.
- 선택한 로트를 발주 메모로 이어 최소 3상자·최대 50상자 범위와 예시 낙찰가 합계를 계산한다. 선별·포장·배송·실제 납품가는 포함되지 않는다고 바로 아래에 적었다.
- 확인되지 않은 가상 이메일 주소는 제거했다. 복사는 클립보드 API가 응답하지 않으면 1.2초 뒤 선택 복사로 전환하며, 성공·실패를 상태 문장으로 알린다.
검증 기준과 현재 증거
-
npm run test:e2e:recent-showcases는 320/390/768/1440 반응형, 터치 타깃, 왼쪽 인셋, 이미지, RFS 프리셋·수동 조정·렌더 전환·감소 모션, 새벽청과 로트·제철·수량 경계·계산·실제 클립보드, 두 페이지 axe·JS 없음·런타임 오류를 검사한다. -
최초 실행에서 CSS 토큰 경로 404와 응답하지 않는 클립보드 권한을 각각 재현했다. 공개 검증에서는 캐시된 이전 RFS 스크립트도 재현해 외부 스크립트 URL에 버전을 붙였다. 토큰을 명시적 스타일 링크로 바꾸고 복사 폴백을 추가한 뒤 성능 계측을 포함해 115/115 통과했다.
-
showcase-audit.mjs rfs-cfd saebyeok-cheonggwa는 두 페이지 × 320/390/768/1440, 총 8/8 통과했다. -
위 전용 E2E는 저장소 전체
verify.mjs의 L6에 포함한다. 배포 전에는 전체 게이트를 다시 실행한다. -
2026-08-26 국소 조정: 배포 텍스트에서 한자를 전부 차단하고, 쇼케이스의 세로 조판·한지/먹·인장/도장·다도 연출은 가로 라벨, 중립 상태 배지, 금속 활자·편집 도구, 독서·정밀 목공 언어로 교체했다. 한국어 고유명사와 역사 소재는 유지하되 동아시아 전통 장식을 제품의 시각 단축어로 쓰지 않는다.
한남자리 · 철판클럽 쇼케이스 듀오 (2026-08-29)
한남자리 — 집 목록이 아닌 개인 브리핑 데스크
- 공개 페이지는
맞춤 브리핑 → 세 집 비교 → 선택 서류철 → 중개사 인계의 다섯 장면으로 끝낸다. 같은 경고와 검수 원장을 공개 페이지에 반복하지 않는다. - 입력 기준은 거래 형태·예산·최소 전용면적·입주 희망일·필요 주차 다섯 개뿐이다. 화면에 없는 조건을 점수나 추천 근거로 사용하지 않는다.
- AI는 조건 관계를 해석하고, 공인중개사는 권리·현장·계약 사실을 확인한다. 분석 전 화면은 중립 상태이며 생성 이미지와 예시 자료는 소유 범위 안에서 바로 표시한다.
- 시각 방향은 석재색 종이 면, 서울의 청회색, 제한된 브론즈 한 색이다. 어두운 면은 마지막 인계 장면에만 써서 검정·금색 부동산 광고 문법을 피한다.
- 관리자 화면은 공개 페이지의 설명을 복제하지 않고 게시 전 사실·표시·AI 근거·변경 이력을 검수한다. 선택 전에는 표가 전체 폭을 쓰고 선택 뒤에만 조사관이 열린다.
철판클럽 — 도심 철판 버거와 운영 조건의 두 갈래
- 공개 페이지는 주문 뒤 눌러 굽고 조립하는 한 끼의 속도와 버거 조합기를 보여 준다. 가맹 페이지는 상권·점포·전용 면적·주방 동선·미정 비용을 먼저 보여 준다.
- 코발트는 서울 도심 간판처럼 제한적으로 쓰고, 크림 종이 면·검정 활자·실제 조리 동선에 가까운 생성 이미지로 국내 빠른 식사 매장의 감각을 만든다.
- 가상의 브랜드이므로 매출·수익률·실제 매장·계약 조건을 꾸미지 않는다. 미정 비용은 미정으로 남기고 문의 전 확인 목록으로 연결한다.
6단계 증거
- 실제 Chromium·WebKit E2E: 90/90 시나리오 · 350/350 단언 · 12/12 캡처 통과.
- 공개·하위 화면 실렌더 감사: 320·390·768·1440에서 8/8 통과. axe, HTML, CSS, JS, 한자 검사도 통과했다.
- 갤러리 카드 네 장과 공유 이미지 두 장은 확정 화면을 실제 Chrome으로 다시 캡처했다.
- 구현 중 발견한 21개 교훈은
research/showcase-duo-learning-ledger-2026-08-29.md에 근거와 함께 남겼고, 재사용 가능한 규칙은packages/skill/references/trustworthy-showcases.md와 관련 게이트 문서에 반영했다.
전 쇼케이스 반응형·여백 재감사 (2026-08-30)
연장 범위와 유지한 방향
이 판은 새 콘셉트로 갈아엎지 않는 연장 경로다. 21개 showcase의 서로 다른 프리셋과 실제/생성 이미지, 정보 구조는 유지하고, 화면이 커지거나 작아질 때 그 의도가 무너지는 지점만 고쳤다. 위의 "기존 16종에 남아 있는 것"은 이 항목으로 대체된다.
- 본문과 정보 표면을 분리했다. 1920·2560px에서 문장·폼은 기존
measure를 지키고, CFD 사양·시세·원장·관리 작업대·히어로처럼 정보 밀도가 필요한 표면만 named wide container로 넓혔다. 전역max-width를 풀어 빈 여백을 만든 곳은 없다. - 가시 내용의 경계를 고쳤다. 버튼·검색·select·저장 동작·모바일 프리셋·지도 핀의 hitbox와 실제 텍스트에 여백을 주고, 첫/마지막 table/flex 셀의 0px padding을 제거했다. 입력 상자의 실제 겹침도 DOM rect로 검사한다.
- 읽기와 접근성을 함께 고쳤다. 저대비 보조 토큰을 WCAG AA에 맞게 조정하고,
transition: all을 대상 속성으로 분리했다. 갤러리의 클릭 div는 overlay button으로 바꾸고, 표 headerscope, skip link, input label/name/autocomplete,prefers-reduced-motion, icon button 이름, 이미지 intrinsic size를 보완했다. - 가상 흐름의 진실 범위를 가깝게 표시했다. 청온·카스크·마테리아의 견적/예약/갤러리 예시는 행동 근처에서 예시·저장/전송 없음 범위를 알리고, 만들어 낸 연락처·기본 개인정보·가짜 평점은 제거했다.
감사기와 기준 화면
tools/showcase-audit.mjs는 기본 폭을 320, 390, 768, 1440, 1920, 2560으로 확장했다. 실제 렌더에서 가로 overflow, 대비, h1, 컨트롤 줄바꿈뿐 아니라 다음을 hard fail로 잰다.
- 버튼 라벨·field value·표/ARIA cell의 가시 좌우 inset(기본 8 CSS px)
- visible input/select/textarea의 면적 겹침
- native/ARIA table cell의 실제 글자와 셀 테두리 사이 여백
CSS color-mix()가 Chromium에서 반환하는 CSS Color 4 color(srgb …)와 반투명 background 합성을 파서에 반영했다. 또 sr-only/aria-hidden 장식/빈 overlay를 Range.selectNodeContents()로 가시 텍스트에 섞어 여백 실패를 만들던 측정 오류를 text node 단위로 고쳤다. 테스트 도구의 false RED를 UI 수정으로 덮지 않는다.
가온의 세 시각 기준은 44px 알림 hitbox와 캘린더·요약 표의 좌우 여백이라는 의도 변경을 실제 current/baseline 캡처로 비교한 뒤에만 갱신했다. tools/visual.mjs --update-baseline=<이름,...>로 대상 기준만 갱신할 수 있게 하여 다른 기준 화면을 재촬영으로 덮지 않는다.
6단계 증거
- 전 쇼케이스 정적 실렌더: 26개 HTML × 6개 폭 = 156/156 PASS
- 저장소 전체
npm run verify: L0~L6 전 계층 PASS. 여기에는 실렌더 156, 메인 디자인 게이트 178, 제품 게이트 15, 시각 회귀 30, WebKit 8, 최근 쇼케이스 E2E 115, TWENTYOZ E2E 148, 쇼케이스 듀오 90 scenario / 350 assertion / 12 capture가 포함된다. npm run build: 성공. 배포 텍스트 한자 검사도 98개 파일 PASS.- 21개 카드의 현재 화면/보조 화면과 듀오 OG를 실제 Chromium으로 다시 캡처했다.
재사용 규칙 승격
문헌 근거와 이번의 RED→GREEN 증거는 designpaca skill의 references/layout.md, references/preflight.md, references/audit-gate.md에 반영했다. 초광폭 레이아웃을 별도 상태로 다루고, WCAG의 최소 target size와 프로젝트의 시각 여백 기본값을 구분하며, 반응형 표·폼·진실 라벨·측정기 정확도를 함께 게이트로 유지한다.
정적 쇼케이스 재구성 · 모달 위치 · 초광폭 이미지 계약 (2026-08-30)
이번 판의 판단
최근의 상태형 작업물(목요일 화원, 마테리아, 한남자리, 철판클럽, RFS, 새벽청과, TWENTYOZ)은 이미 각 도메인의 정보 구조와 상호작용이 충분히 구분되어 있어 새 문법으로 덮지 않았다. 대신 그 안의 팝업 위치·짧은 화면·초광폭 수축 결함만 고쳤다. 오래된 정적 쇼케이스는 다음 10개를 실제 정보 표면과 첫 화면의 판단 근거가 보이도록 재구성했다.
baekje-celadon,font-foundry,form-architecture,hyang-incense: 전시·활자·도면·기록이 첫 시야에서 행동과 함께 읽히도록 재배치lumina-optics,orbit-aerospace,pulsegate,synapse-bci: 빛/비행/접근/임상이라는 주장에 대응하는 실제 시험 조건·표본·한계를 판독 레지스터로 고정muwol-coffee,overtone-sound: 장식적 영웅면 대신 로스터의 좌표/원장과 설치 아카이브의 조건을 정보 밀도로 구성
사용자 보고에서 고정한 계약
- 마테리아의 native dialog는 전역 reset이 UA
margin을 지워 좌상단에 붙었던 결함이었다. 프로젝트 modal과 한남 듀플렉스 gallery lightbox에fixed + inset:0 + margin:auto와 viewport 상한·내부 scroll owner를 명시했다. 390·1440·2560에서 중심 오차 0~0.008px, 배경 scroll lock을 자동 단언한다. - 가온의 알림 메뉴, 한남자리 공개 메뉴와 관리자 조사관은 390×320에서 화면 밖/0px scroll 상태가 되지 않도록 viewport-bound와 short-height 구성을 분리했다.
- 철판클럽은 2560에서 hero
1680px, copy688px, media856.7px, 다음 content rail1400px으로 고정했다. 사진을 화면 반까지 무한 확장하지 않으며,stage-wide/media-wide/copy-wide/workbench-wide를 구분한다.
재발 방지와 증거
tools/audit.mjs가 마테리아 두 modal의 중심/scroll lock을 390·1440·2560에서, 한남자리 짧은 화면 scroll owner와 가온 알림 메뉴를 390×320에서, 철판클럽 hero/media/content rail을 2560에서 hard fail로 검사한다. 이 규칙은 공통 스킬의 layout.md, preflight.md, audit-gate.md에 승격했다. 모달의 포커스·키보드·닫기 계약은 WAI-ARIA APG를 근거로 한다.
공개 캐시 확인
정적 CSS의 stable URL은 HTML만 새로 배포해도 사용자 브라우저에 이전 스타일을 남길 수 있다. 이번 수정의 main/site/tokens/franchise/landing/admin 링크와 CSS @import 버전을 함께 올렸다. 공개 검증은 HTTP 200이 아니라 새 stylesheet href와 새 토큰·핵심 computed value가 실제로 로드됐는지까지 확인한다.
후속 정착 프레임 캡처·초광폭 보정 (2026-08-30)
- 모든 쇼케이스 카드·OG 캡처 경로를
tools/capture-stable.mjs로 통합했다.file://즉시 스크린샷 대신 no-store 로컬 HTTP에서 폰트·이미지 decode·페이지별 bounded settle을 기다린 뒤, reduced-motion 환경과 video/audio·WAAPI·rAF 정지로 완성된 한 프레임을 저장한다. local 4xx, pageerror, 깨진 이미지는 결과물 생성 실패로 취급한다. - 철판클럽의 2560px 히어로는 기존 1680px cap이 실제로 지나치게 좁아 양쪽에 맹탕을 만들었다. 히어로만
2160px, 카피900px, 미디어1120px으로 분리하고 메뉴/본문 레일은1400~1480px에 남겼다. 넓은 화면은 하나의 전역 상한이 아니라, 시선의 중심이 되는 히어로와 읽기/정보 표면이 각각 다른 폭을 소유한다. - 이미지 생성은 제공된 실제 사진을 대체하지 않는다. 브랜드가 요구하는 정확한 장면·광학적 분위기 같은 시각 자산이 실제로 비어 있고, 생성물·연출 라벨을 함께 둘 수 있을 때만 이미지 바이블의 슬롯별로 생성한다. 이번 두 결함은 각각 레이아웃과 WebGL 역할의 문제라서 생성 이미지로 덮지 않는다.
사용자 보고로 보강한 과정 분할 계약 (2026-08-30)
- 철판클럽의
process는 hero와 별도의 텍스트/이미지 분할 표면이다. 기존 모바일/중간 폭의padding-left: calc((100vw - --wrap) / 2)가 초광폭 규칙에 남아 2560px에서 실제 제목·설명 너비를 12px로 만들었다. hero만 통과한 것은 이 결함의 증거가 아니다. - 초광폭 과정 표면은
--stage-wide1680px 안에서 copy 822px, media 856px로 제한하고 양쪽 logical padding을 함께 재설정했다. 제목은 의도한 2줄, 목록 문장은 읽는 폭으로 돌아왔으며 사진은 화면 절반까지 팽창하지 않는다. tools/audit.mjs는 hero뿐 아니라 이처럼 DOM/여백 규칙이 다른 각 text-media split의 rail, copy/media 폭, 실제 제목 폭과 줄 수를 2560px에서 hard fail로 잠근다. 재사용 스킬의 preflight·layout·audit 규칙도 같은 원리로 보강했다.
사용자 코멘트로 정정한 제품 카피와 이미지 계약 (2026-08-30)
- 가상 흐름이라도 사용자가 하려는 일이 아닌 제작 목적을 문구로 노출하지 않는다.
화면 동작용,쇼케이스,저장처럼 구현을 설명하는 말은실제 주문·결제·저장을 제공하지 않습니다처럼 행동과 한계를 함께 이해할 수 있는 말로 바꾼다. - 조합 결과에 영속 동작이 없으면 저장 버튼이나 성공 상태를 만들지 않는다. 다음 행동은 실제로 가능한 범위 안에서 가상 매장 안내처럼 목적이 분명한 경로로 연결한다.
- CSS 조각으로 제품 자체를 흉내 내지 않는다. 철판클럽의 싱글 버거는 전용 4:3 생성 이미지(
assets/single-smash-generated.webp,960×720)로 바꾸고 카드 안에생성 이미지라벨을 붙였다. 제품 사진은 제품을 고르는 근거이고, DOM 도식은 비교·설명에만 쓴다. - 조합기처럼 반복되는 fieldset은 경계선만으로 묶지 않는다. 다음 fieldset 앞에 16px의 명시적 간격을 두고 1217px 실화면에서 legend와 이전 선택지가 붙어 보이지 않는지 확인한다.
홍보 사이트 전면 재설계 — Quiet Editorial Proof Surface (2026-08-30)
문제와 방향
기존 홍보 화면의 떠 있는 유리 GNB, 의미가 약한 수치 메뉴, 화면을 덮는 로그, 서로 경쟁하는 장식은 제거했다. 새 방향은 조용한 편집 지면 위에 실제 결과 화면이 색과 밀도를 소유하는 증거 중심 사이트다. 차가운 종이색, 헤어라인 그리드, 흑연색 본문, 신호용 적색 한 색으로 구조를 만들고, 어두운 면은 7단계 과정 구간에만 사용한다.
기준 레퍼런스는 세 역할로 나눠 측정했다.
- Framer: 1440px에서 약 1200px 콘텐츠 레일, 64px GNB, 54/54px·500 헤드라인처럼 첫 화면의 밀도와 직접 탐색 구조를 참고했다.
- Stripe Press: 제품의 실제 표지가 색을 소유하고 주변 인터페이스는 편집 지면으로 물러나는 방식을 이미지 우선 증거 표면에 적용했다.
- Instrument: 48px 외곽 여백과 넓은 그리드, 제한된 모션처럼 큰 화면의 여백이 빈 공간이 아니라 위계를 만드는 방식을 참고했다.
스위스 미니멀을 구조의 기준으로 삼고, 실제 프로젝트 화면을 비대칭 편집 레이아웃에 배치했다. 다른 디자인 스킬의 장식 문법을 한 화면에 합치지 않았으며, 프로젝트 design.md의 사실성·성능·검증 계약이 항상 우선한다.
시스템 계약
- 배경
#f2f1eb, raised#faf9f4, 본문#171b19, muted#5e655f, accent#c43f20, focus#1267d6를 단일 토큰으로 고정한다. - 본문과 라틴 모두 Wanted Sans Variable을 주 서체로 사용하고 400·470·520만 쓴다. 굵은 600/700과 브라우저 기본 서체를 디자인 상태로 사용하지 않는다. JetBrains Mono는 설치 명령에만 쓴다.
- 웹폰트 실패 시의 시스템 폰트는 디자인 선택이 아니라 복원 경로다. 실제 페이지 카피를 100px/400으로 측정해 Windows 한글 85.2%, macOS 한글 93.7%, 라틴 102.3%
size-adjust를 적용한다. - 본문 control은 최소 44px, 버튼 텍스트 좌우 inset은 최소 8px, 작은 화면의 콘텐츠 gutter는 최소 20px다. 제목은 한국어
keep-all, 필요한 식별자만anywhere를 쓴다. - GNB는 전체 폭 불투명 sticky bar다. 4개 핵심 경로를 모바일에서도 직접 보여 주며, 적은 항목을 햄버거 안에 숨기거나 floating glass로 만들지 않는다.
정보 구조·카피·다국어
- 홈은
가치 제안 → 실제 결과 → 7단계 과정 → 검증 근거 → 설치순서다. 공개 수치7단계,15개 하드 게이트,KO / EN은 실제 구현과 단일 기준 데이터에 연결한다. - 쇼케이스는 대표 화면 한 개와 나머지 20개 결과를 분리해 같은 작품이 연속 반복되지 않게 한다. 전체 21개 작업의 이름·분류·설계 방향·리스크·alt는 slug 기반 fail-closed 영어 사전과 한국어 원본을 공유한다.
- 영어판의 실제 캡처 안 한국어는 원본 프로젝트 인터페이스다. 이를 번역된 화면처럼 꾸미지 않고 결과 섹션의 설명과 대표 캡션에
Original Korean-language interfaces라고 명시한다. - 의미가 불분명한 구호,
full log, 가짜 성과 수치, 제작자 관점의 변명, 과장된 저작권 문구를 제거한다. 라이선스 표기는 저장소의 실제 MIT 상태만 말한다. - canonical, 한국어/영어 hreflang, x-default, OG/Twitter 이미지와 설명, JSON-LD 언어를 각 경로의 실제 콘텐츠와 일치시킨다.
이미지·효과·모션
- hero는 캡처 콜라주가 아니라 designpaca의
0~6검증 게이트를 일곱 장의 공간 판으로 보여 주는 전용 inline SVG다. 실제 결과 캡처는 바로 다음 섹션으로 내려 원본 정보 구조가 잘리지 않게 하고, 카드 목록은cover / top, 펼친 상세 화면은 전체 판독이 목적이므로contain을 쓴다. - 새 이미지 생성은 하지 않았다. 실제 화면이 이미 더 강한 증거이고, 생성물로 결과를 꾸미면 프로젝트 사실 경계가 흐려지기 때문이다.
- Three.js 버전도 별도 프로토타입으로 렌더링해 비교했다. 약
132KB gzip의 런타임을 더하면서도 이 장면에서는 흔한 게이트 터널로 읽혀 채택하지 않았다. 같은 의미를 DOM에 가까운 SVG로 더 선명하게 전달하고 WebGL 실패 분기를 없애는 판단이다. - SVG 필터는 전체 화면 효과가 아니라 종이결의
feTurbulence와 적색 신호·등고선의 작은feDisplacementMap에만 쓴다. 배경에는 opacity0.025의 정적 SVG grain을 유지한다. - hero의 일곱 판은 8px 이내로 한 번 조립되고 적색 스캔도 한 번만 끝난다.
prefers-reduced-motion에서는 둘 다 제거하며 지속 rAF나 무한 주변 모션은 만들지 않는다.
빌드·검증 계약
- 4개 공개 경로는
/,/en/,/showcase/,/en/showcase/다. - 반응형 기하는 320·390·768·1024·1440·1920·2560px에서 검사하고, 한국어/영어 홈·쇼케이스의 390·1440px 전체 화면을 정확히 8장 캡처한다.
- 브라우저 게이트는 실제 text node 기준 h1 줄 수, 문서/요소 overflow, 44px target, 버튼 inset, opaque sticky GNB, 키보드 순서와 focus occlusion, copy 성공·권한 실패, axe A/AA, hreflang/SEO, 감소 모션, 효과 제거, 외부 폰트 실패 폴백, 이미지 decode와 placeholder를 hard fail로 검사한다.
- Chromium의 단일 장문
fullPage캡처는 디코드가 끝난 화면 밖 이미지를 회색 surface로 기록할 수 있다. 따라서 증거 캡처는 실제 viewport PNG를 타일로 합성하고, 최종 PNG 안의 각 이미지 rect 픽셀 분산을 다시 검사한다. DOM의complete만으로 시각 통과를 판정하지 않는다. - 최종 로컬 증거
2026-08-30T08-43-03-335Z에서 80/80 시나리오, 계약 단언 995/995와 hard lock 5개를 합친 1000/1000 단언, 8/8 캡처가 통과했다. 390px lab LCP/CLS는 홈 한국어236ms/0, 홈 영어224ms/0, 쇼케이스 한국어104ms/0, 쇼케이스 영어104ms/0이다. - 빌드 산출물은 외부 JS 청크가 없고, 홈 상호작용은 작은 inline script만 사용한다. CSS는 경로별 분할하며 폰트 family는 Wanted Sans와 JetBrains Mono 두 개로 제한한다.
- 배포는 이 작업의 범위가 아니다. 프로덕션 빌드와 로컬 실제 브라우저 증거가 통과해도 공개 배포 상태를 대신하지 않는다.
사용자 시각 검수 정정 — 홍보 증거 표면 (2026-08-30)
- 앞선
실제 결과 화면우선 판단은 폐기한다. 홈은 프로젝트 캡처를 모두 제거하고 방향 결정·실제 토큰 이름·검사 계약을 하나의 비대칭 산출물 표면으로 보여 주며, 기존 화면은 고객 성과처럼 말하지 않는설계 예시아카이브에만 남긴다. 한국어·영어 홈의 산출물 구간에 이미지가 없고 접근 가능한 표본 세 개가 있는지를 E2E 하드 게이트로 고정한다. OG/Twitter도 프로젝트 캡처를 쓰지 않고capture-site-og.mjs가 한국어·영어 실제 히어로를 각각 1200×630으로 다시 만든다. 2026-08-30· 국소 — 기존 히어로의 8px 일회성 조립 계약을 대체한다. Chrome 공식 stacking-cards 예시의 transform-only scrub 원칙을 참고하되 sticky 구간이나 빈 스크롤 길이는 만들지 않고, 7개 판·등록 핀·신호선을scroll(root block)에 묶는다. 데스크톱은 문서 스크롤0→18vh, 68rem 이하는0→58vh에서 분산 상태와 조립 완료 상태 사이를 양방향으로 연속 보간하며, 미지원 브라우저·prefers-reduced-motion은 정적인 조립 완료 상태를 사용한다. KO/EN 변화·역스크롤 원복·감소 모션 불변·이펙트 제거 상태를 E2E 하드 게이트로 고정한다.2026-08-30· 국소 — 히어로 아래 방향 결정·토큰 조판·검증 프레임과 7단계 진행 레일도 각각의 named CSS view timeline에 연결한다. 본문과 수치는 움직이지 않고 방향선·글립·토큰 표본·검사 화살표·짧은 진행선만 transform/opacity로 보간하며, 검증 프레임의 가장 넓은 판은 두 작은 판을 맞추는 고정 기준면으로 남긴다. 모바일 검증 완료 지점은 sticky GNB 아래 제목 여유 24px 이상에서 끝나도록 앞당기고, 미지원 브라우저·감소 모션·이펙트 제거 상태는 정적 완료 화면을 사용한다. KO/EN 전진·역스크롤 geometry 8개, 320~2560px 반응형, effects-off와 감소 모션을 80/80 시나리오·1000/1000 단언으로 고정한다.2026-08-30· 국소 — 사용자 체감 실패를 반영해 방향 결정·토큰과 조판의 timeline subject를 article에서 실제.direction-stage·.type-stage로 내리고, 방향 면·벡터·세 노드와 글립·세 토큰 표본을 각 자식의 첫 가시 시점 이후에 서로 다른 범위로 조립한다. 글자 대비는 opacity로 낮추지 않으며 미지원·감소 모션·이펙트 제거 상태는 정적 완료 화면이다. KO/EN × 390/1440에서 110px 실제 wheel 전진·역진, offscreen 진행 상한, 가시 구간 dwell, 독립 위상과 token stagger를 새 E2E 계약으로 추가했고 전체 85/85 시나리오·1094/1094 단언을 통과했다.2026-08-30· 사용자 실화면 정정 — Direction의 한 줄짜리 독립 변형을 폐기하고, 정확한 세 점 좌표를 공유하는 두 SVG 선분으로 나눠 시작점을 고정한 채 스크롤로 길이만 드러낸다. 노드는 이동하지 않고 앞 선분이 좌표에 닿은 뒤 같은 중심에서 나타나며, 모바일도xMidYMid meet로 비율을 보존한다. 마지막 선과 점은cover 80% / 86%안에 끝내 sticky GNB 아래에서 완성 상태를 읽을 시간을 남긴다. Tokens는 글립·기준선·세 역할 표본, Verification은 두 가변 프레임·네 검사 화살표, Process는 작은 행 장식 대신 목록 전체 높이의 연속 진행선을 각 named view timeline으로 보간한다. 실제 앱 브라우저 1316×1272에서 110px 전진·역진과 Direction endpoint 연결을 확인했고, KO/EN 320~2560px 전체 게이트는 86/86 시나리오·1122/1122 단언·8/8 캡처로 통과했다.
디자인 전문성 근거와 홍보 카피 정렬 (2026-09-12)
- Quiet Editorial Proof Surface의 밝은 Wanted Sans·신호 적색·이미지 없는 산출물 표본 세 개와 히어로의 양방향 스크롤 계약은 그대로 둔다. 고객 성과처럼 읽힐 수 있는 프로젝트 캡처를 홈에 되돌리지 않으며, 실제 사이트는 낮은 위계의
/showcase/또는/en/showcase/링크로 직접 찾는다. - 히어로는 웹 디자인 원리와 실제 사례 조사, 브랜드 맥락에 맞는 방향 결정, 반응형 구현, 실화면 검증을 한 제품 흐름으로 설명한다. 이 순서는 NN/g의 사용성 휴리스틱으로 확인하는 보편 기준과, 프로젝트 목적에 맞춰 선택하는 시각 스타일을 구분하는 근거를 드러낸다.
- 검증 근거는 수나 전환 성과로 포장하지 않는다.
7은 0~6 파이프라인이고, 나머지 두 사실은 근거·맥락의 분리와 기능 검사/실화면 검토의 분리다. Webby Awards 심사 기준의 콘텐츠·구조와 내비게이션·시각 디자인·기능성·상호작용·전반 경험 구분도 화면의 인상만으로 품질을 단정하지 않는 보조 근거로 쓴다. - 세부 검사에는 320px, 대비, 키보드, 감소 모션, 200% 확대, 실제 전후 화면의 구성·위계·반응형 동작, 출처 연결을 포함한다. 기존의 토큰·SEO 검사는 유지하되, 프로젝트에 따라 달라지는 검증 범위를 단일 하드 게이트 수로 홍보하지 않는다.
- 보존한 계약: 실제 고객 성과 주장 없음, 홈 산출물에는 이미지 없음, 히어로 그래픽과 모션은 변경하지 않음, 한국어·영어는 같은 정보 구조와 목적지 링크를 가짐. 빌드한 사이트 E2E는 86개 시나리오·1,122개 단언·8개 캡처를 통과했고, 개발 서버의 별도 증거는 KO/EN × 320·390·768·1440·1920·2560에서 overflow 0, 새 링크 12/12의 44px 이상 hit area와 키보드 목적지 2/2를 확인했다. 실제 클립보드 권한은 이 headless 환경에서 거절되어 두 언어 모두 선택 복사 안내와 전체 명령 선택으로 안전하게 전환됨을 확인했다. 새 캡처와 JSON은
outputs/design-expertise-20260912/home/에 보관한다.
확대 텍스트 표본 보정 (2026-09-12)
- 홈의 Direction 표본 문장은 절대 배치와
overflow: clip조합에서 200% 확대 시 잘릴 수 있었다. 문장을position: relative의 normal flow로 옮기고 기존 왼쪽 48%·너비를 유지했다. stage는display: flow-root로 첫 자식 margin의 외부 collapse를 막는다. 기본 2:1 표본은 stage width 기준margin-top: 24%,margin-bottom: 5%로 기존 중앙 위치를 보존하며, 긴 문장은 stage 높이를 자연스럽게 늘린다. 모바일 16:9에서는margin-top: 27%, 28rem 이하에서는 기존 왼쪽 39%·너비 55%를 유지한다.