1135 lines
104 KiB
Markdown
1135 lines
104 KiB
Markdown
# 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. `999px` pill 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단계에서 실제로 걸린 것
|
||
|
||
정적 검사와 실렌더를 둘 다 돌렸다. **걸린 것을 전부 적는다 — 유리한 것만 고르면 리스크가 자기 홍보가 된다.**
|
||
|
||
1. **하드 게이트 #3 위반** — radius 4종(`sm`/`md`/`shell`/`999px`). 2종으로 정리
|
||
2. **하드 게이트 #1 위반** — `base.css` 에 토큰 밖 `rgb()` 7곳. 토큰으로 올림
|
||
3. **하드 게이트 #7 위반** — 헤더 "English" 버튼 **2.59:1**. `--ink-muted` → `--ink` 로 올려 통과
|
||
4. **카운트 규칙 위반** — 히어로 h1 이 4줄이었다(상한 2줄). 폰트 크기를 낮춰 2줄
|
||
5. **셰이더 결함** — 판이 정면으로 서 있어 `dot(n, view) ≈ 1`, **프레넬이 구조적으로 0**이었다.
|
||
유리로 읽히게 하는 유일한 신호가 죽어 있었고 그것이 "그냥 흐릿한 사각형"의 원인이다.
|
||
법선을 미세하게 굽히고 UV 경계로 테두리 광을 따로 만들어 해결
|
||
6. **셰이더 UV 오류** — `gl_FragCoord` 를 렌더 타겟 크기(캔버스의 0.6배)로 나누고 있었다. UV 가 1 을 넘어 텍스처가 잘렸다.
|
||
`getDrawingBufferSize()` 로 교정
|
||
7. **폭 리듬 부재** — 9개 섹션 중 파이프라인이 **1440px 중 625px(43%)** 만 쓰면서 높이는 1637px 로 가장 길었다.
|
||
페이지의 본체가 가장 좁았다. 2열로 열어 1113px
|
||
8. **페이지가 실은 수치가 틀렸다** — "타입 단계 5단계" 라고 실었는데 실측 **9종**이었다.
|
||
이 페이지의 리스크가 "자기 수치를 공개한다" 인데 공개한 값이 틀리면 리스크가 아니라 허위다
|
||
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_GAP` 6.4, 카메라 시작 z 7.2
|
||
|
||
## 다음 실행이 해야 할 일
|
||
|
||
1. **배포 후 Lighthouse 로 첫 인터랙션·LCP·CLS 를 재고 "미측정" 을 채운다.** 채우기 전까지 비워둔다
|
||
2. 폰트를 CDN 의존에서 자체 호스팅으로 옮길지 판단한다 (현재 jsDelivr · Fontshare · Google Fonts 3곳)
|
||
3. OG 이미지가 없다
|
||
4. 섹션이 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 <use> 미러), 모든 수치에 측정조건, 보류 시험까지 공개 |
|
||
| Synapse | dark-instrument | 래스터는 SIM 명시, 미측정값 '—', 윤리 섹션을 콘텐츠로 |
|
||
|
||
### 프리플라이트 (기계 검증)
|
||
|
||
- 정적 grep: 슬롭 지문 0건, `100vh` 0건, 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) 변환로 페이지당 28~220KB.
|
||
- 헤드리스 캡처에서 WebGL이 필요한 페이지는 `--disable-gpu` 금지,
|
||
`--use-angle=swiftshader` 사용(capture-shots.mjs `webgl: true`).
|
||
|
||
### 다음 실행이 해야 할 일
|
||
|
||
1. 비전 감사에서 남은 사소한 제안(차트 축 라벨 크기, 사진 해상도 아쉬움)은
|
||
기록만 남김 — 구조 결함 아님.
|
||
2. 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).
|
||
- 상세는 네이티브 <dialog>+showModal — Esc·포커스 트랩·inert 를 브라우저가 처리
|
||
(<details> 메뉴와 같은 결정론). 배경 탭·닫기 버튼은 인라인 JS 몇 줄.
|
||
- 모달은 불투명 판(유리 금지 — 메뉴 시트와 같은 교훈), ::backdrop 만 블러.
|
||
큰 CTA "사이트 보기" = btn-solid + 둥근 화살 칸(cmd-copy 아이콘 어휘), 새 탭.
|
||
- 카드 전체 클릭은 절대배치 <button> 한 개(카드당) — 텍스트는 비활성, 탭 순서=카드 순서.
|
||
- 검증 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 이동, 검색·상태칩 필터, 학생 상세 모달
|
||
(메모 추가 폼), 채점 모달(저장 시 표 상태 '채점 대기→채점 완료' 갱신). 모달은
|
||
네이티브 <dialog> — 쇼케이스 모달과 같은 결정론(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`의 UA `margin: 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 안 <p> 구문 위반, 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)·명시적 앵커(</title>)를 쓴다. ③ 배포 아티팩트(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(탭바 5~6·시트·safe area), Material 3(바텀내비 3~5·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종 제작 및 쇼케이스 아카이브에 등록:
|
||
|
||
1. **청온 케어 (`cheongon-care`)**
|
||
- **프리셋**: `ledger-console` + `swiss-minimal`
|
||
- **강조색**: 에메랄드 틸 (`#0D9488`)
|
||
- **컨셉**: 임상 수준의 6단계 위생 프로토콜과 투명한 정찰제 견적, 실시간 고객 신청-관리자 관제 대시보드 풀-루프 연동 및 32개 QC 체크리스트 모달.
|
||
2. **캐스크 아카이브 (`cask-archive`)**
|
||
- **프리셋**: `editorial-archival` + `dark-instrument`
|
||
- **강조색**: 싱글 몰트 앰버 골드 (`#D99B38`)
|
||
- **컨셉**: Gordon & MacPhail, Signatory, SMWS 독립병입자 공식 수입 인증 보증서, 캐스크 넘버 원장 실시간 검색, 1:1 도슨트 테이스팅 예약 및 이벤트/인스타그램 연동.
|
||
3. **마테리아 스튜디오 (`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% 즉시 노출**.
|
||
- **모바일 (<860px)**:
|
||
- 상단 풀블리드 섬네일 커버 + 헤더 직하단 CTA 블록.
|
||
- 하단 고정 스티키 액션 독(`dialog-sticky-dock` + `.dialog-dock-btn`)을 적용하여 스크롤 중에도 엄지 영역에 상시 '사이트 보기' 버튼 고정.
|
||
- **카드 그리드 즉시 진입**:
|
||
- 카드 썸네일 호버 시 우측 하단에 `.card-quick-visit` 알약 버튼 노출.
|
||
- 타이틀 행에 `.card-direct-link` 아이콘 버튼을 배치하여 모달을 열지 않고도 바로 새 탭에서 데모를 체험할 수 있도록 다이렉트 액션 강화(`z-index: 4`).
|
||
- **글로벌 리셋 보강**:
|
||
- `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 로 화면 밖이면 정지, 탭이 숨어도 정지
|
||
|
||
**세 번 고쳤고 원인은 전부 단위계였다.**
|
||
|
||
1. **파티클이 0.2초 만에 사라졌다.** `dt` 를 "프레임 단위"로 넘기면서 수명 감쇠를
|
||
`age -= dt * 0.115` 로 걸었다. dt≈0.9 라 프레임당 0.1씩 줄어 10프레임이면 소멸이다.
|
||
**dt 를 초로 못박고** 속도의 단위를 "초당 격자 셀"로 통일해 해결했다.
|
||
감쇠도 프레임 상수(0.9965)에서 `pow(base, dt*60)` 으로 바꿨다 — 주사율이 물리를 바꾸면 안 된다.
|
||
2. **입자가 실 한 가닥으로 뭉쳤다.** 전부 급기구에서만 재생성해서 같은 경로를 따라갔다.
|
||
방이 아니라 소용돌이 그림이 됐다. 절반만 급기구에서, 나머지는 방 안 임의 위치에서 되살린다.
|
||
3. **핫 아일/콜드 아일이 안 보였다.** 랙이 위로 뿜게 했더니 냉기와 열기가 랙을 사이에 두고
|
||
갈라지지 않았다. 전면 흡입 → 배면 토출로 바꿔 이 업계의 기본 그림이 화면에 나오게 했다.
|
||
|
||
수치는 전부 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) 있다. 같은 프리셋의 셋째는 결정이 아니라 카테고리 평균이다
|
||
|
||
### 다음 실행이 해야 할 일
|
||
|
||
1. `tools/verify.mjs` 의 L2~L6 은 여전히 관리 앱 2종(가온·두레) 전용이다. 정적 쇼케이스 17종은 L0 두 겹과 `showcase-audit` 만 돈다 — 시각 회귀 기준을 쇼케이스로 넓힐 여지가 있다
|
||
2. LCP·CLS 는 세 페이지 모두 **미측정**이다. 배포 후 모바일 프로파일 Lighthouse 로 재야 한다
|
||
3. `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-meta` 13px 모노 3.57:1, `synapse-bci` 의 11~12px 캡션 3.84~3.98:1, `thursday-flowers` 의 `.visit-mark` **1.33:1**(크림 #DDD7CA 글자가 크림 #F8F6F0 배경 위에 있다. 장식용 세로쓰기 표시지만 사실상 안 보인다)
|
||
- **히어로 헤드라인 줄 수 초과** — `pulsegate` 320px 에서 4줄 / 1440px 에서 3줄, `synapse-bci` 3줄 (카운트 규칙 상한 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와 응답하지 않는 클립보드 권한을 각각 재현했다. 토큰을 명시적 스타일 링크로 바꾸고 복사 폴백을 추가한 뒤 성능 계측을 포함해 **115/115 통과**했다.
|
||
- `showcase-audit.mjs rfs-cfd saebyeok-cheonggwa`는 두 페이지 × 320/390/768/1440, 총 **8/8 통과**했다.
|
||
- 위 전용 E2E는 저장소 전체 `verify.mjs`의 L6에 포함한다. 배포 전에는 전체 게이트를 다시 실행한다.
|