feat(skill): 자율 검증 폐쇄 루프 내재화 — 0.6.0
Some checks failed
ci / build (push) Failing after 5s

- 핵심 규칙 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 실패 시 배포 금지 체인
This commit is contained in:
Yun Chan 2026-08-22 21:22:05 +09:00
parent 78ddc8b61a
commit 72337b7ee0
226 changed files with 18212 additions and 380 deletions

View file

@ -0,0 +1,303 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>스튜디오 폼 · 공간건축연구소</title>
<meta
name="description"
content="형태는 침묵하고 치수가 태도를 증명한다. 비례와 구조의 명료성으로 설계하는 건축연구소의 프로젝트 인덱스와 도면."
/>
<meta property="og:title" content="스튜디오 폼 · 공간건축연구소" />
<meta
property="og:description"
content="비례와 구조의 명료성으로 설계하는 건축연구소."
/>
<meta property="og:type" content="website" />
<meta name="theme-color" content="#FDFDFB" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>
<link rel="stylesheet" href="./styles/main.css?v=2" />
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect x='6' y='6' width='20' height='20' fill='none' stroke='%23111' stroke-width='2'/%3E%3Crect x='11' y='11' width='10' height='10' fill='%23111'/%3E%3C/svg%3E"
/>
</head>
<body>
<a class="skip" href="#main">본문으로 건너뛰기</a>
<!-- 4모서리 등록 표시(인쇄 판맞춤) — 이 페이지의 리스크. 넓은 화면에서만. -->
<div class="corner corner-tl" aria-hidden="true"></div>
<div class="corner corner-tr" aria-hidden="true"></div>
<div class="corner corner-bl" aria-hidden="true"></div>
<div class="corner corner-br" aria-hidden="true"></div>
<header class="site-head">
<div class="wrap head-inner">
<a class="wordmark" href="#main">스튜디오 폼 <span class="wordmark-en">FORM</span></a>
<nav class="nav" aria-label="섹션">
<a href="#index">인덱스</a>
<a href="#drawing">도면</a>
<a href="#photo">사진</a>
<a href="#contact">연락</a>
</nav>
</div>
</header>
<main id="main">
<!--
서문 — Pawson의 교훈: 크기가 아니라 여백과 무게로 위계를 만든다.
큰 글자는 이 페이지에 하나만 있다.
-->
<section class="hero" aria-labelledby="hero-h">
<div class="wrap">
<p class="hero-meta reveal">
공간건축연구소 · 서울 · 2014년 설립
</p>
<h1 id="hero-h" class="reveal" style="--delay: 60ms">
형태는 침묵하고<br />치수가 태도를 증명한다
</h1>
<p class="hero-statement reveal" style="--delay: 120ms">
폼은 공간을 꾸미지 않습니다. 벽이 서는 이유, 창이 열리는 방향, 보가
건너뛰는 길이를 설계합니다. 아름다움은 그 결정들이 모였을 때의
부산물입니다 — 그래서 우리의 홍보는 도면과 치수로 합니다.
</p>
<dl class="hero-facts reveal" style="--delay: 180ms">
<div>
<dt>설립</dt>
<dd>2014년</dd>
</div>
<div>
<dt>구성</dt>
<dd>건축사 4 · 연구원 3</dd>
</div>
<div>
<dt>영역</dt>
<dd>주거 · 미술관 · 재생</dd>
</div>
</dl>
</div>
</section>
<!--
인덱스 — 이 페이지의 주인공. 사진 없는 역연도 목록.
R1(johnpawson.com): 프로젝트는 제목/위치/연도의 세 줄 텍스트다.
-->
<section id="index" class="section" aria-labelledby="index-h">
<div class="wrap">
<h2 id="index-h" class="index-h reveal">프로젝트 인덱스</h2>
<ol class="index">
<li class="index-row reveal">
<a href="#drawing">
<span class="idx-name">폼 하우스</span>
<span class="idx-loc">경기 가평</span>
<span class="idx-year">2025</span>
</a>
</li>
<li class="index-row reveal" style="--delay: 40ms">
<a href="#drawing">
<span class="idx-name">백령도 해녀기념관</span>
<span class="idx-loc">인천 백령도</span>
<span class="idx-year">2024</span>
</a>
</li>
<li class="index-row reveal" style="--delay: 80ms">
<a href="#drawing">
<span class="idx-name">은평 재생주거</span>
<span class="idx-loc">서울 은평</span>
<span class="idx-year">2024</span>
</a>
</li>
<li class="index-row reveal" style="--delay: 120ms">
<a href="#drawing">
<span class="idx-name">제주 돌담 스튜디오</span>
<span class="idx-loc">제주 애월</span>
<span class="idx-year">2023</span>
</a>
</li>
<li class="index-row reveal" style="--delay: 160ms">
<a href="#drawing">
<span class="idx-name">성수 인쇄공장 재생</span>
<span class="idx-loc">서울 성수</span>
<span class="idx-year">2022</span>
</a>
</li>
<li class="index-row reveal" style="--delay: 200ms">
<a href="#drawing">
<span class="idx-name">폼 오피스</span>
<span class="idx-loc">서울 종로</span>
<span class="idx-year">2019</span>
</a>
</li>
</ol>
</div>
</section>
<!--
도면 — 스티키 평면도가 섹션을 지나는 동안 그어진다.
R3: CSS scroll-driven + stroke-dashoffset. 폴백은 완성된 도면.
도면은 장식이 아니라 이 연구소의 언어 그 자체다.
-->
<section id="drawing" class="section drawing" aria-labelledby="drawing-h">
<div class="wrap drawing-grid">
<div class="drawing-stage reveal">
<svg
viewBox="0 0 520 640"
role="img"
aria-label="폼 하우스 1층 평면도. 남쪽으로 긴 거실과 북쪽 서비스 블록, 동쪽 마당으로 이루어져 있다."
>
<!-- 외곽 벽 (이중선) -->
<path
class="wall"
d="M 80 60 H 440 V 580 H 80 Z M 88 68 H 432 V 572 H 88 Z"
/>
<!-- 내부 벽 -->
<path
class="wall inner"
d="M 260 68 V 300 M 260 380 V 572 M 88 300 H 432 M 88 380 H 260"
/>
<!-- 문 개구부 표시 -->
<path class="door" d="M 260 340 h -36 M 260 340 a 36 36 0 0 1 36 -36" />
<!-- 기둥 -->
<path class="column" d="M 176 130 h 10 v 10 h -10 z M 356 130 h 10 v 10 h -10 z M 176 250 h 10 v 10 h -10 z M 356 250 h 10 v 10 h -10 z" />
<!-- 치수선 -->
<g class="dim">
<path d="M 80 610 H 440 M 80 602 v 16 M 440 602 v 16" />
<path d="M 50 60 V 580 M 42 60 h 16 M 42 580 h 16" />
<text x="260" y="632" text-anchor="middle">14,400</text>
<text x="30" y="325" text-anchor="middle" transform="rotate(-90 30 325)">9,600</text>
</g>
<!-- 방 이름 -->
<g class="room-label">
<text x="174" y="190">거실</text>
<text x="350" y="190" text-anchor="middle"></text>
<text x="174" y="460">주방·식당</text>
<text x="350" y="455">서비스</text>
<text x="350" y="490">현관</text>
</g>
<!-- 방위 -->
<g class="north">
<path d="M 470 90 v -40 M 470 50 l -8 14 M 470 50 l 8 14" />
<text x="470" y="110" text-anchor="middle">N</text>
</g>
</svg>
<p class="drawing-caption">폼 하우스 · 1층 평면도 · mm</p>
</div>
<div class="drawing-notes">
<h2 id="drawing-h">폼 하우스, 가평</h2>
<p>
대지 남쪽의 소나무군을 지우지 않기 위해 집은 길게 눕고, 마당은 집
안쪽으로 들어왔습니다. 구조는 9.6m 축 한 번으로 건너뛰어 거실에 기둥이
없습니다.
</p>
<p>
벽체는 단열 성능의 요구치에서 두께가 정해졌습니다 — 마감 재료를 먼저
고르고 두께를 맞추지 않았습니다. 창은 남향 긴면 하나, 동쪽 마당 면
하나. 그 두 개로 이 집의 하루가 결정됩니다.
</p>
<dl class="drawing-spec">
<div>
<dt>구조</dt>
<dd>RC 라멘 · 9.6m 스팬</dd>
</div>
<div>
<dt>면적</dt>
<dd>132.4 ㎡ (40.0평)</dd>
</div>
<div>
<dt>창개구</dt>
<dd>남측 14.4m · 동측 4.2m</dd>
</div>
<div>
<dt>줄공사</dt>
<dd>2025-04 2026-01</dd>
</div>
</dl>
</div>
</div>
</section>
<!--
사진 — 흑백 3점. 그리드는 엄격하게, 크기는 사진이 요구하는 대로.
-->
<section id="photo" class="section photo" aria-labelledby="photo-h">
<div class="wrap">
<h2 id="photo-h" class="reveal">사진</h2>
<div class="photo-grid">
<figure class="ph ph-a reveal">
<img
src="./assets/pavilion-bw.webp"
alt="긴 수평창이 낸 콘크리트 파빌리온에 벽 그림자가 지는 모습"
width="1440"
height="810"
loading="lazy"
/>
<figcaption>폼 하우스 · 남측 · 준공 후 첫 여름</figcaption>
</figure>
<figure class="ph ph-b reveal" style="--delay: 60ms">
<img
src="./assets/interior-bw.webp"
alt="천창으로 한 줄기 빛이 사선으로 들어오는 미장 마감 전시실"
width="900"
height="1200"
loading="lazy"
/>
<figcaption>백령도 해녀기념관 · 제1전시실</figcaption>
</figure>
<figure class="ph ph-c reveal" style="--delay: 120ms">
<img
src="./assets/model-bw.webp"
alt="스터디 모델 — 직육면체를 쌓은 백색 석고 모형에 측광이 지는 모습"
width="1100"
height="825"
loading="lazy"
/>
<figcaption>스터디 모델 07 · 석고</figcaption>
</figure>
</div>
</div>
</section>
<!-- 연락 — 서류 같은 마무리 -->
<section id="contact" class="section contact" aria-labelledby="contact-h">
<div class="wrap contact-grid">
<h2 id="contact-h">연락</h2>
<address>
서울 종로구 자하문로 00길 00, 3층<br />
<a class="link" href="mailto:studio@form.kr">studio@form.kr</a>
</address>
<p class="contact-note">
신규 프로젝트는 분기에 두 건만 받습니다. 대지와 예산의 조건을 적어
보내주시면, 가능 여부를 2주 안에 답합니다.
</p>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-inner">
<p>스튜디오 폼 · 공간건축연구소</p>
<p class="foot-meta">© 20142026</p>
</div>
</footer>
<script>
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) {
if (!e.isIntersecting) continue;
e.target.classList.add("is-in");
io.unobserve(e.target);
}
},
{ rootMargin: "0px 0px -8% 0px", threshold: 0.05 },
);
for (const el of document.querySelectorAll(".reveal")) io.observe(el);
</script>
</body>
</html>