소개 페이지 — 스킬을 스스로 적용해 만든 첫 사례

designpaca 파이프라인 0~6단계를 실제로 돌려 만들었다. 근거는 research/site-brief.md,
결정 기록은 apps/site/design.md 에 있다.

한 문장 컨셉
- 이 페이지는 designpaca 를 거쳐 만들어졌고, 절차와 형태 수치를 한국어 기준으로 전부 공개한다

번호 없이 순서 표현 (C-07 회피)
- 파이프라인 6단계를 번호로 매기면 검출률 3위 슬롭 지문에 걸린다
- 시각: 스파인 위 위치가 순서를 만든다 / 내용: 같은 브리프가 단계마다 변한다
- 의미: <ol role="list"> — 화면에 번호 0개, 보조기술에는 "6개 중 n번째"

멀티링구얼
- / = 한국어, /en/ = 영어. 번역이 아니라 각 판을 따로 썼다
- 언어별 레이아웃이 자기 폰트만 로드한다 (판당 2개)
- 조판 섹션은 한국어판에서 주장이고 영어판에서 사례다

실측
- JS 314B (gzip, 예산 50KB) / CSS 3.0KB / HTML 4.4~4.8KB
- radius 2종 · backdrop-filter 0개 · 타입 5단계 · 강조색 1개
- 슬롭 지문 grep 9패턴 0건, 정적 하드 게이트 8개 전부 통과

감사에서 걸려 고친 것
- eyebrow 6개 → 상한 ceil(7/3)=3 위반 → 3개로
- clip-path 리빌 → 게이트 #8 위반 → transform 으로 교정
- 구분선 대비 1.30:1 → 입력 테두리용 별칭 분리 (WCAG 1.4.11)
- 본문 컬럼 960px → --measure 로. layout.md 예시 코드도 같이 수정
This commit is contained in:
Yun Chan 2026-08-20 11:00:38 +09:00
parent 8808c672dc
commit 6103700d38
18 changed files with 1497 additions and 20 deletions

View file

@ -0,0 +1,152 @@
---
import type { Content } from "../i18n/content";
interface Props {
c: Content["pipeline"];
}
const { c } = Astro.props;
---
{/*
순서를 번호 없이 표현하는 세 층:
① 시각 — 스파인 위의 위치가 순서를 만든다 (Beck 튜브맵)
② 내용 — 같은 브리프가 단계마다 변해간다 (IKEA 조립 설명서)
③ 의미 — <ol role="list"> — 화면에 번호는 없지만 보조기술은 "6개 중 n번째"로 읽는다
role="list" 는 list-style:none 일 때 Safari/VoiceOver 가 목록 의미를 버리는 것을 되돌린다
*/}
<section class="section" aria-labelledby="pipeline-h">
<p class="eyebrow">{c.eyebrow}</p>
<h2 id="pipeline-h">{c.h2}</h2>
<p class="lead pipeline-lead">{c.lead}</p>
<div class="brief">
<span class="brief-label">{c.briefLabel}</span>
<p class="brief-text">{c.brief}</p>
</div>
<ol role="list" class="spine">
{
c.stages.map((s) => (
<li class="stop">
<h3 class="stop-name">{s.name}</h3>
<p class="stop-does">{s.does}</p>
<p class:list={["stop-becomes", { mono: s.mono }]}>{s.becomes}</p>
</li>
))
}
</ol>
<p class="after muted">{c.after}</p>
</section>
<style>
.pipeline-lead {
margin-bottom: var(--space-6);
}
.brief {
border: 1px solid var(--line-strong);
border-radius: var(--radius-md);
padding: var(--space-4);
margin-bottom: var(--space-2);
}
.brief-label {
display: block;
font-size: var(--step-0);
letter-spacing: var(--tracking-label);
color: var(--ink-muted);
margin-bottom: var(--space-2);
}
.brief-text {
margin: 0;
font-size: var(--step-1);
}
/* ---- 스파인 ---------------------------------------------
선은 장식이 아니라 순서 그 자체다. 정거장의 위치가 번호를 대신한다.
---------------------------------------------------------- */
.spine {
list-style: none;
margin: 0;
padding: 0 0 0 var(--space-5);
position: relative;
}
.spine::before {
content: "";
position: absolute;
left: 5px;
top: var(--space-4);
bottom: var(--space-4);
width: 1px;
background: var(--line-strong);
}
.stop {
position: relative;
padding-block: var(--space-4);
}
.stop + .stop {
border-top: 1px solid var(--line);
}
/* 정거장 표시 — 채워진 원. 숫자가 아니다 */
.stop::before {
content: "";
position: absolute;
left: calc(var(--space-5) * -1);
top: calc(var(--space-4) + 0.55em);
width: 11px;
height: 11px;
border-radius: var(--radius-sm);
background: var(--surface);
border: 3px solid var(--accent);
box-sizing: border-box;
}
.stop-name {
font-size: var(--step-1);
margin-bottom: var(--space-2);
}
.stop-does {
color: var(--ink-muted);
margin-bottom: var(--space-3);
}
/* 브리프가 이 단계에서 무엇이 됐는가 — 변화가 순서를 만든다 */
.stop-becomes {
margin: 0;
padding-left: var(--space-3);
border-left: 2px solid var(--accent);
}
.stop-becomes.mono {
font-family: var(--font-mono);
font-size: var(--mono-size);
overflow-wrap: anywhere;
}
.after {
margin-top: var(--space-5);
}
/* 진입 연출은 하나뿐이고, 없어도 전부 읽힌다.
scroll-driven animation 이라 scroll 리스너가 없다. */
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {
.stop {
animation: stop-in linear both;
animation-timeline: view();
animation-range: entry 10% cover 22%;
}
@keyframes stop-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
}
}
</style>