- 핵심 규칙 6: 완료의 정의는 게이트 통과. 사용자를 QA로 쓰지 않는다 - 5단계 기계 검사 4번째: design-gate 실행 의무화 - references/audit-gate.md: 사고-검사 매핑, SEO/meta 체크리스트, OS/브라우저 특성, 하니스 규칙 - tools/design-gate.mjs: 범용 게이트(메타/SEO·대비·수축·리듬·트랙·스케일×폭 + 옵션 L0/L3/L4, checks 깊은 병합) - 리듬·트랙 불변식: 숫자 라벨 등폭·빈 셀 트랙 균일·등간격 — 시간표 자동배치 결함 재현 픽스처 검출, 앱 15뷰 통과(오탐 0) feat(site): 관리 앱 2종 프로덕션 콘솔(v6→v18) - 가온 학적부 9뷰·두레 수강신청 6뷰: shadcn 문법, Pretendard/Noto Serif/IBM Plex 폰트 전략, 볼드 금지(400/500/600), WCAG AA 대비 전면 교정, 한글 keep-all 조판 - LMS 필수 요소(알림 센터·공지·진도·평가 유형·출결 사유·학점 경고), 12명 기준 데이터 정합, SEO 구조(h1 유일·OG/twitter·og.png) - 시간표 자동배치 결함 수리(명시적 격자 좌표) - QA 게이트: L0 stylelint/html-validate · L1 단위 30 · L2 감사 61+불변식 · L3 시각회귀 30화면 · L4 WebKit · L5 키보드 탐색 — npm run verify 실패 시 배포 금지 체인
9.4 KiB
1단계 — 레퍼런스 조사 (오버톤 사운드랩)
브리프 (2줄)
미디어아트 & 공간 음향 DSP 스튜디오 "오버톤 사운드랩" 사이트. 다크 #0E0D0B 배경에 앰버 1색(#E8A33D) 강조, 의도적으로 깨진 격자(anti-grid)와 비대칭 타이포, FFT 파형 레이어가 특징. 조사일 2026-08-21, WebFetch + CSS 원문 직접 다운로드로 실측. 접근 불가 사이트는 아래 "실패 기록"에 정직하게 남김.
R1 · 구조 — Carsten Nicolai https://carstennicolai.de
(같은 업종 확인: 사운드아트/미디어아트 아티스트 — 독일, noton 레이블 운영. 미디어아트 + 공간 음향 작업 = 오버톤과 동일 군)
가져올 축: 29년치 프로젝트를 연도 인덱스로 나열하는 아카이브 구조, 3분할 플로트 컬럼, 섹션 순서.
실측값 (cn.css / basic.css / navi.css 원문 다운로드로 확인)
- 컨테이너:
--main-width: 1152px(주석 "1280 - 128"), 좌우 패딩 32px → 실효 폭 1088px, 중앙 정렬 - 컬럼 분할:
#main-navi15% 좌측 /#sub-navi20% /#content85% 우측 플로트(works 페이지는 content 65%). 합계 100%가 아닌 비대칭 분할 - 내비 항목 8개: news / biography / works / publications / links / contact / imprint / newsletter — 쿼리스트링 라우팅(
?c=works&w=slug) - 섹션 순서: news → works 아카이브(사이트의 핵심, 연도 역순 2024→1996, 연도 29개 / 작업 약 140건, 최다 2010년 13건) → publications → links → contact → imprint
- 색: 배경 #fff / 본문 #333 / 보조 #666 / 보더 #999·#ccc / 호버·셀렉션 #cf3 (형광 차트뤼스 1색 — 단일 액센트 원칙의 선례)
- 타입: Gotham(클라우드 웹폰트), 기본 13px / h1 18px / h3 16px / h4 15px / 저작권 10px → 최대:최소 = 1.8:1 (극도로 압축된 스케일), line-height 1.5em
- 이미지: publications 3열 그리드(
100%/3, 갭 8px×2), 최대 512×512px, 1px outline(rgba(50,50,50,0.15)); 비디오는 padding-top 56.25% (16:9) 반응형 래퍼 - 모션: 최소화 — 링크 호버 배경 #cf3 + opacity 0.75, 햄버거 슬라이드 0.2s ease-in-out뿐.
@keyframes show페이드인 1개 - 브레이크포인트: 1024px(내비 햄버거 전환), 767px(2열, 패딩 12px)
보조 관찰 — Zimoun (tonelab.jp 요청이 zimoun.ch로 연결되어 관찰됨, 자세한 내막은 실패 기록): 홈페이지 전체가 약 135행의 텍스트 하이퍼링크 세로 인덱스 — "작품명, 도시, 국가, 연도" 행 패턴, 썸네일 없음, 페이지네이션 없음. 사운드아트 스튜디오의 극단적 텍스트 인덱스 사례.
변형 (오버톤 적용): 연도 역순 프로젝트 인덱스 구조는 그대로 가져오되, carstennicolai의 정돈된 15/20/65 분할 대신 항목마다 좌우 오프셋을 다르게 주는 anti-grid 인덱스로 깨기. #cf3 단일 액센트 원칙은 앰버 #E8A33D로 치환. 13px 기본 타입의 압축된 스케일 감각은 유지하되 타이틀만 비대칭 대형으로 대비.
R2 · 톤 — Obys Agency https://obys.agency
(업종 다름 확인: 디지털 디자인 에이전시 — 사운드/미디어아트 스튜디오가 아님. anti-grid/에디토리얼 파격 톤 축)
가져올 축: 엇갈린 2열 스태거, 모노크롬 + 단색 강조, 라인 마스크 텍스트 리빌, 마퀴 루프.
실측값 (HTML + 인라인 CSS 원문 다운로드로 확인)
- 색:
--white:#fff; --black:#000— 순수 2색 모노크롬. HTML 전체에서 발견된 hex는 #fff, #000, #ffffff 단 3개뿐 - 폰트: 자체 호스팅 커스텀 서체
ObysSans4.woff2(font-display: swap, fallback serif) - 비대칭 배치: 포트폴리오 번호가 두 인터리브 열로 분리(한 열 01, 03, 05… / 다른 열 02, 04, 06…) — 2열이 한 행(~50%)씩 어긋난 스태거. 두 번째 번호 시퀀스는 80까지 올라가는 스크롤 레일 마커
- 타입 크기 대비: 정적 마크업 기준 3티어 관찰(프로젝트 타이틀 대형 / 카테고리 라인 "Creative Direction, Web Design/Dev" / 2자리 인덱스 01–19 소형 카운터). px 수치는 JS 런타임 주입이라 정적 HTML로 실측 불가 — 정직하게 "측정 불가"로 기록
- 여백/헤어라인:
--m-x:1rem여백 변수, 구분선 border-bottom 1.34px (의도적 소수점 헤어라인), 프리로더 진행바 2.5px - 모션: 라인 마스크 리빌(부모 overflow:hidden + 자식
transform:translateY(102%)에서 0으로), 프로젝트 목록 2회 복제 = 심리스 무한 마퀴/캐러셀 루프, 프리로더 카운터mix-blend-mode:difference, 푸터 실시간 시계("CET 00:00 AM") - 에디토리얼 제스처: 푸터에 "Vertical, Horizontal, Grid" 문구 — 그리드 자체를 주제로 하는 자기지시적 모티프
변형 (오버톤 적용): 스태거 2열 + 마퀴를 프로젝트 인덱스에 도입하되 화이트 모노크롬을 #0E0D0B 다크로 반전, difference 블렌드는 앰버에 적용하면 시인성 붕괴 위험 → 앰버는 마퀴 호버 하이라이트에만. 1.34px 소수점 헤어라인, 2.5px 진행바 같은 미세 수치 감각 그대로 차용.
R3 · 디테일 — Canvas 2D 파형 그리기 (CSS-Tricks)
https://css-tricks.com/making-an-audio-waveform-visualizer-with-vanilla-javascript/
기법 (Canvas 2D, SVG 아님):
- 그리기: 터틀 그래픽 모델 —
moveTo/lineTo/arc로 세그먼트당 "중앙선에서 세로 선 → 세그먼트 폭의 반원 호 → 중앙선 복귀".(i+1) % 2의 isEven 플래그로 막대가 중앙선 위/아래 교차, 호 방향도 반전.ctx.translate(0, canvas.offsetHeight/2 + padding)로 원점을 중앙 좌측으로 이동해 수학 단순화. lineWidth 1, 흰색, 패딩 20px - 오디오 처리: AnalyserNode 실시간 FFT가 아니라 사후 피크 추출 —
fetch()mp3 →arrayBuffer()→decodeAudioData()→getChannelData(0)(모노) → 70개 블록으로 나눠 블록별 절대값 평균 → 최대값의 역수로 정규화(최음이 1이 되게) →draw(normalize(filter(audioBuffer))) - DPR:
dpr = window.devicePixelRatio || 1, 백킹스토어 = 표시 크기 × dpr 후ctx.scale(dpr, dpr) - 전체 코드 100줄 미만
구현 제약 (반드시 반영):
- 전체 mp3 다운로드가 끝나야 그리기 시작 (프로그레시브 불가)
- 30분 팟캐스트 ≈ 1억 5,876만 샘플(44.1kHz) — 메인 스레드에서 수십만~수백만 연산, 수 초 소요 및 다른 연산 차단
- 리사이즈 핸들러 없음 (튜토리얼에 없음 — 직접 구현 필요)
- Safari는
webkitAudioContext폴백 필요 - 저자 스스로 "매우 비실용적"이라 인정. 완화책: 서버 사전 피크 계산(npm audioform), IntersectionObserver/사용자 인터랙션 시점 지연, 정적 플레이스홀더(실제 Megaphone의 파형은 가짜이며 팟캐스트 전부 동일)
오버톤 적용: 실오디오 디코딩은 하지 않는다. 소규모 사전 계산 피크 JSON(행별 70~120 봉) 또는 결정적 절차적 유사-FFT(시드 노이즈 + 대역 마스크)를 캔버스로 그리고, 리사이즈 리드로우와 IntersectionObserver 지연만 직접 추가.
회색조 위계 (#0E0D0B 기준)
| 역할 | 값 | 용도 |
|---|---|---|
| 배경 | #0E0D0B | 전역 |
| 패널/레이어 | #14120F | 파형 레이어 뒤 패널 |
| 헤어라인/보더 | #2A2723 | 1.34px 헤어라인, 인덱스 구분 |
| 3차 텍스트(메타) | #5C5750 | 연도, 좌표, 인덱스 번호 |
| 2차 텍스트 | #9A948A | 프로젝트 설명, 카테고리 |
| 본문 텍스트 | #EDEAE4 | 웜 화이트 본문 (순백 #fff 회피) |
| 강조 1색 | #E8A33D | 활성 파형, 호버, 수치 — 소수 지점만 |
R1(#333/#666 3단 텍스트 + #cf3 1색)과 R2(#000/#fff 이분법)의 절충: 다크에서 5단 회색 + 앰버 1색.
안 할 것 (3개)
- 액센트 2색 이상·컬러 그라디언트 — carstennicolai(#cf3 단일)와 obys(모노크롬) 선례처럼 앰버 #E8A33D 1색으로 제한. 파형 레이어도 앰버 농도/투명도 변주만.
- 균등 3열 카드 그리드·대칭 모듈 격자 — anti-grid 성격 위반. 허용: 항목별 오프셋이 다른 스태거 인덱스(carstennicolai 15/20/85 비대칭 + obys 열 스태거 참조).
- 전 구간 실오디오 디코딩 파형 — mp3 전체 다운로드 후 메인 스레드 decodeAudioData는 수 초 블록(R3 제약). 소규모 사전 피크 데이터 또는 절차적 유사-FFT로 대체.
실패 기록
- tonelab.jp: 1회차 WebFetch 60초 타임아웃. 2회차(tonelab.jp/) 응답은 zimoun.ch 콘텐츠로 연결되어 반환됨(해당 도메인이 zimoun.ch를 가리키는 것으로 관찰). 구조 데이터는 R1 보조 관찰로만 사용.
- www.zimoun.net: ECONNREFUSED(217.70.184.38:443) 2회 → 버림.
- bureau-owen.com / www.bureau-owen.com: DNS ENOTFOUND 2회 → 버림.
- davidlangewis.com / www.davidlangewis.com: DNS ENOTFOUND 2회 → 버림.
- yugo.vin / yugo.vin/: DNS ENOTFOUND 2회 → 버림.
- 대체 후보 1곳 시도: obys.agency — 성공(R2로 채택).
- carstennicolai.de: 1회차 성공하나 본문만으로 색·폭 실측 불가 → CSS 4종(curl) 중 3종 다운로드 성공(color.css는 0바이트, 미사용으로 판단), cn/basic/navi.css에서 실측값 추출.
- obys.agency 실측 한계: 타입 px 크기·컨테이너 폭은 JS 런타임 주입이라 정적 HTML로 실측 불가 — 구조적 티어만 관찰하고 수치는 기재하지 않음(지어내지 않음).