소개 페이지 5단계 감사 — 실렌더에서 결함 3건
정적 검사로는 안 잡히고 320px 실렌더에서만 나온 것들이다. 발견 내용은 페이지의 프리플라이트 로그 섹션에 그대로 실었다. 1. 320px 가로 오버플로 13px 표를 overflow 컨테이너로 감쌌는데도 넘쳤다. grid item 의 기본 min-width 가 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 이 있어야 overflow-x 가 실제로 작동한다. 2. 다크 밴드 버튼 대비 2.88:1 (기준 4.5:1) 밴드가 --accent 만 반전시키고 --accent-ink 는 흰색으로 뒀다. --accent-ink-inverse 토큰을 추가해 함께 반전시킨다. 6.21:1 로 회복. 3. 파이프라인 6단계 중 뒤 3개가 영영 opacity 0 ← 가장 심각 animation-range 의 cover 22% 에 페이지 하단 요소는 도달하지 못한다. 스크롤을 끝까지 내려도 이 페이지의 본체가 절반 사라진 상태였다. 이펙트를 끄면 보이지만 켜면 사라지는 것은 폴백이 있는 것과 다르다. 스크롤 진입 연출을 제거했다. 남은 모션은 호버·포커스뿐이다. 하드 게이트 12/12 전부 통과 (8개 정적 + 4개 실렌더). 실렌더 대비 최저 6.21:1, AA 미달 0건.
This commit is contained in:
parent
6103700d38
commit
6e05e69ca4
7 changed files with 61 additions and 36 deletions
|
|
@ -72,18 +72,21 @@
|
||||||
|
|
||||||
| 이펙트 | 구현 | 폴백 |
|
| 이펙트 | 구현 | 폴백 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 스파인 진입 페이드 + 8px 상승 | `animation-timeline: view()`, JS 0 | 미지원 브라우저에서는 아무 일도 안 일어난다. 콘텐츠는 처음부터 visible |
|
|
||||||
| 복사 버튼 | 인라인 314B | 클립보드 권한 실패 시 텍스트를 선택 상태로 만들어 사용자가 직접 복사 |
|
| 복사 버튼 | 인라인 314B | 클립보드 권한 실패 시 텍스트를 선택 상태로 만들어 사용자가 직접 복사 |
|
||||||
|
|
||||||
`prefers-reduced-motion: no-preference` 안에 `@supports (animation-timeline: view())` 로 이중 게이팅.
|
**스파인 진입 페이드는 넣었다가 뺐다.** `animation-timeline: view()` 에 `animation-range: entry 10% cover 22%` 를 걸었는데, **페이지 하단 요소는 `cover 22%` 에 영영 도달하지 못한다.** 스크롤을 끝까지 내려도 6개 정거장 중 뒤 3개가 opacity 0 으로 남았다 — 이 페이지의 본체가 절반 사라진 것이다.
|
||||||
|
정적 검사로는 안 잡혔다. 320px 실렌더에서 스크롤을 끝까지 돌려보고야 나왔다.
|
||||||
|
**이펙트를 끄면 보이지만 켜면 사라지는 것은 폴백이 있는 것과 다르다.** 이 페이지에 남은 모션은 호버·포커스 상태 변화뿐이다.
|
||||||
|
|
||||||
## 5단계 감사 결과
|
## 5단계 감사 결과
|
||||||
|
|
||||||
- 하드 게이트: **정적 검사 가능한 8개 전부 통과.** 나머지 4개(가로 스크롤·버튼 줄바꿈·실렌더 대비·테마 반전)는 브라우저 측정 대기
|
- 하드 게이트: **12개 전부 통과.** 8개는 정적 검사, 4개(가로 스크롤·버튼 줄바꿈·실렌더 대비·테마 반전)는 **320px 실렌더**로 측정
|
||||||
- 슬롭 지문 grep 9개 패턴: **0건**
|
- 슬롭 지문 grep 9개 패턴: **0건**
|
||||||
- 카운트 규칙: 섹션 7 / eyebrow 3 (상한 3) / 마퀴 0 / h1 1 — 통과
|
- 카운트 규칙: 섹션 7 / eyebrow 3 (상한 3) / 마퀴 0 / h1 1 — 통과
|
||||||
|
- 실렌더 대비: 본문·링크·버튼 전부 AA 이상. 최저 6.21:1 (다크 밴드 버튼)
|
||||||
|
|
||||||
감사 중 걸려서 고친 것 4건은 페이지의 "프리플라이트 로그" 섹션에 그대로 실려 있다.
|
감사 중 걸려서 고친 것 **7건**은 페이지의 "프리플라이트 로그" 섹션에 그대로 실려 있다.
|
||||||
|
**뒤 3건은 정적 검사로는 안 잡히고 실렌더에서만 나왔다** — 이 사실 자체가 `preflight.md` 의 "프레임워크 프로젝트는 프로덕션 빌드 결과에 돌린다" 규칙을 뒷받침한다.
|
||||||
|
|
||||||
## 의도적으로 하지 않은 것과 그 이유
|
## 의도적으로 하지 않은 것과 그 이유
|
||||||
|
|
||||||
|
|
@ -100,9 +103,8 @@
|
||||||
## 다음 실행이 해야 할 일
|
## 다음 실행이 해야 할 일
|
||||||
|
|
||||||
1. **배포 후 Lighthouse 로 첫 인터랙션·LCP·CLS 를 재고, "이 페이지의 수치" 섹션의 `—` 를 채운다.** 채우기 전까지 그 항목은 비워둔다
|
1. **배포 후 Lighthouse 로 첫 인터랙션·LCP·CLS 를 재고, "이 페이지의 수치" 섹션의 `—` 를 채운다.** 채우기 전까지 그 항목은 비워둔다
|
||||||
2. 브라우저로 나머지 하드 게이트 4개를 검사하고 `8/12` 를 갱신한다
|
2. 폰트를 CDN 의존에서 자체 호스팅으로 옮길지 판단한다 (현재 jsDelivr · Fontshare · Google Fonts 3곳)
|
||||||
3. 폰트를 CDN 의존에서 자체 호스팅으로 옮길지 판단한다 (현재 jsDelivr · Fontshare · Google Fonts 3곳)
|
3. OG 이미지가 없다. 공유될 자리라면 만든다
|
||||||
4. OG 이미지가 없다. 공유될 자리라면 만든다
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const { command, copyLabel, copiedLabel, note } = Astro.props;
|
||||||
<style>
|
<style>
|
||||||
.cmd {
|
.cmd {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
border: 1px solid var(--line-strong);
|
border: 1px solid var(--line-strong);
|
||||||
|
|
@ -27,7 +28,7 @@ const { command, copyLabel, copiedLabel, note } = Astro.props;
|
||||||
max-width: 34em;
|
max-width: 34em;
|
||||||
}
|
}
|
||||||
.cmd-text {
|
.cmd-text {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: var(--space-3);
|
padding: var(--space-3);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
@ -38,6 +39,7 @@ const { command, copyLabel, copiedLabel, note } = Astro.props;
|
||||||
/* 독립 컨트롤이므로 44px 이상 */
|
/* 독립 컨트롤이므로 44px 이상 */
|
||||||
min-width: 5.5em;
|
min-width: 5.5em;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
|
white-space: nowrap;
|
||||||
padding-inline: var(--space-3);
|
padding-inline: var(--space-3);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-left: 1px solid var(--line-strong);
|
border-left: 1px solid var(--line-strong);
|
||||||
|
|
|
||||||
|
|
@ -128,25 +128,10 @@ const { c } = Astro.props;
|
||||||
margin-top: var(--space-5);
|
margin-top: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 진입 연출은 하나뿐이고, 없어도 전부 읽힌다.
|
/* 스크롤 진입 페이드를 넣었다가 뺐다.
|
||||||
scroll-driven animation 이라 scroll 리스너가 없다. */
|
`animation-range: entry 10% cover 22%` 로는 페이지 하단 요소가 cover 22% 에
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
영영 도달하지 못해, 6개 정거장 중 뒤 3개가 opacity 0 으로 잠겼다.
|
||||||
@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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ const { c } = Astro.props;
|
||||||
.demo {
|
.demo {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
|
||||||
margin-bottom: var(--space-5);
|
margin-bottom: var(--space-5);
|
||||||
}
|
}
|
||||||
.demo-col {
|
.demo-col {
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ export const ko: Content = {
|
||||||
{ label: "강조색", value: "1개", note: "딥 틸. 라이트·다크는 같은 색의 대응값이다" },
|
{ label: "강조색", value: "1개", note: "딥 틸. 라이트·다크는 같은 색의 대응값이다" },
|
||||||
{ label: "히어로까지 JS", value: "314B", note: "gzip. 예산 50KB의 0.6%. 복사 버튼 하나뿐이다" },
|
{ label: "히어로까지 JS", value: "314B", note: "gzip. 예산 50KB의 0.6%. 복사 버튼 하나뿐이다" },
|
||||||
{ label: "슬롭 지문 grep", value: "0건", note: "9개 패턴 검사" },
|
{ label: "슬롭 지문 grep", value: "0건", note: "9개 패턴 검사" },
|
||||||
{ label: "하드 게이트", value: "8/12", note: "정적으로 검사 가능한 8개. 나머지 4개는 브라우저 측정 대기" },
|
{ label: "하드 게이트", value: "12/12", note: "12개 전부 검사. 4개는 320px 실렌더로 측정했다" },
|
||||||
{ label: "첫 인터랙션", value: "—", note: "미측정. 프로덕션 배포 후 Lighthouse 로 잰다" },
|
{ label: "첫 인터랙션", value: "—", note: "미측정. 프로덕션 배포 후 Lighthouse 로 잰다" },
|
||||||
],
|
],
|
||||||
caveat: "값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다 — 지어낸 숫자보다 구멍이 정직하다.",
|
caveat: "값은 프로덕션 빌드에서 측정한다. 아직 측정하지 않은 항목은 비워둔다 — 지어낸 숫자보다 구멍이 정직하다.",
|
||||||
|
|
@ -200,8 +200,20 @@ export const ko: Content = {
|
||||||
caught: "본문 컬럼이 960px — 우리가 정한 한 줄 길이(561px)를 넘는다",
|
caught: "본문 컬럼이 960px — 우리가 정한 한 줄 길이(561px)를 넘는다",
|
||||||
fixed: "그리드의 본문 폭을 --measure 로 바꿨다. 스킬 문서의 예시 코드도 같이 고쳤다",
|
fixed: "그리드의 본문 폭을 --measure 로 바꿨다. 스킬 문서의 예시 코드도 같이 고쳤다",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
caught: "320px 에서 가로로 13px 넘쳤다 — 표를 overflow 컨테이너로 감쌌는데도",
|
||||||
|
fixed: "grid item 의 기본 min-width 는 auto 라 콘텐츠가 부모를 밀어낸다. min-width:0 을 걸어야 overflow-x 가 실제로 작동한다",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caught: "다크 밴드 안 버튼의 대비가 2.88:1 — 기준은 4.5:1",
|
||||||
|
fixed: "밴드가 강조색만 반전시키고 그 위 글자색은 흰색 그대로였다. 반전 대응 토큰을 하나 더 두고 함께 바꿨다",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caught: "스크롤을 끝까지 내려도 파이프라인 6단계 중 뒤 3개가 opacity 0 으로 남았다 — 이 페이지의 본체가 절반 사라졌다",
|
||||||
|
fixed: "animation-range 의 cover 22% 에 페이지 하단 요소는 영영 도달하지 못한다. 이펙트를 끄면 보이지만 켜면 사라지는 것은 폴백이 있는 것과 다르다. 연출을 뺐다",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
note: "네 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 이 목록이 짧아 보인다면 그건 이 페이지가 작기 때문이다.",
|
note: "일곱 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 마지막 셋은 정적 검사로는 안 잡히고 320px 실렌더에서만 나왔다.",
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
eyebrow: "한글 조판",
|
eyebrow: "한글 조판",
|
||||||
|
|
@ -300,7 +312,7 @@ export const en: Content = {
|
||||||
{ label: "accent colours", value: "1", note: "deep teal. Light and dark are the same colour" },
|
{ label: "accent colours", value: "1", note: "deep teal. Light and dark are the same colour" },
|
||||||
{ label: "JS to first paint", value: "314B", note: "gzip. 0.6% of the 50KB budget. One copy button" },
|
{ label: "JS to first paint", value: "314B", note: "gzip. 0.6% of the 50KB budget. One copy button" },
|
||||||
{ label: "slop fingerprints", value: "0", note: "9 patterns checked" },
|
{ label: "slop fingerprints", value: "0", note: "9 patterns checked" },
|
||||||
{ label: "hard gates", value: "8/12", note: "the 8 that are statically checkable. 4 await browser measurement" },
|
{ label: "hard gates", value: "12/12", note: "all twelve checked. Four were measured on a real 320px render" },
|
||||||
{ label: "time to interactive", value: "—", note: "not measured yet. Lighthouse, after deploy" },
|
{ label: "time to interactive", value: "—", note: "not measured yet. Lighthouse, after deploy" },
|
||||||
],
|
],
|
||||||
caveat: "Measured on the production build. Unmeasured entries stay empty — a hole is more honest than an invented number.",
|
caveat: "Measured on the production build. Unmeasured entries stay empty — a hole is more honest than an invented number.",
|
||||||
|
|
@ -326,8 +338,20 @@ export const en: Content = {
|
||||||
caught: "The text column was 960px — wider than the measure we had just set (561px)",
|
caught: "The text column was 960px — wider than the measure we had just set (561px)",
|
||||||
fixed: "Bound the grid's main column to --measure. The skill's own example code was wrong too, so that got fixed as well",
|
fixed: "Bound the grid's main column to --measure. The skill's own example code was wrong too, so that got fixed as well",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
caught: "13px of horizontal overflow at 320px — even with the table inside an overflow container",
|
||||||
|
fixed: "A grid item's default min-width is auto, so content pushes the parent wider. overflow-x only works once min-width:0 is set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caught: "The button inside the dark band sat at 2.88:1 — the threshold is 4.5:1",
|
||||||
|
fixed: "The band inverted the accent colour but left the text on it white. Added an inverted counterpart token and switched both together",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caught: "Scrolling to the very bottom still left the last 3 of 6 pipeline stages at opacity 0 — half of this page's core section was gone",
|
||||||
|
fixed: "Elements near the page bottom never reach cover 22% in animation-range. Visible with effects off but missing with them on is not the same as having a fallback. The effect was removed",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
note: "All four were caught while building this page, and fixed after being caught. If the list looks short, that is because the page is small.",
|
note: "All seven were caught while building this page, and fixed after being caught. The last three only surfaced on a real 320px render — static checks missed them.",
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
eyebrow: "Your language is probably not Latin",
|
eyebrow: "Your language is probably not Latin",
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,17 @@ th {
|
||||||
font-size: var(--step-0);
|
font-size: var(--step-0);
|
||||||
letter-spacing: var(--tracking-label);
|
letter-spacing: var(--tracking-label);
|
||||||
}
|
}
|
||||||
/* 좁은 화면에서 표가 페이지를 밀지 않게 — 하드 게이트 #5 */
|
/* 좁은 화면에서 표가 페이지를 밀지 않게 — 하드 게이트 #5.
|
||||||
.table-scroll { overflow-x: auto; }
|
min-width:0 이 없으면 grid item 의 기본 min-width:auto 때문에
|
||||||
|
overflow-x:auto 가 걸려 있어도 페이지 자체가 넓어진다. */
|
||||||
|
.table-scroll {
|
||||||
|
overflow-x: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 같은 이유로 그리드·플렉스 자식은 전부 줄어들 수 있어야 한다 */
|
||||||
|
.page > *,
|
||||||
|
.section > * { min-width: 0; }
|
||||||
|
|
||||||
/* ---- 유틸 -------------------------------------------------- */
|
/* ---- 유틸 -------------------------------------------------- */
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
--accent: #0E6B63; /* 딥 틸. 대비 6.04:1 */
|
--accent: #0E6B63; /* 딥 틸. 대비 6.04:1 */
|
||||||
--accent-ink: #FFFFFF; /* 강조 위 글자. 6.36:1 */
|
--accent-ink: #FFFFFF; /* 강조 위 글자. 6.36:1 */
|
||||||
--accent-inverse: #3FA89D; /* 밴드 위 강조. 6.30:1 */
|
--accent-inverse: #3FA89D; /* 밴드 위 강조. 6.30:1 */
|
||||||
|
--accent-ink-inverse: #0B1A18; /* 밴드 위 강조 위의 글자. 6.43:1 */
|
||||||
|
|
||||||
/* ---- 형태: radius 2종 (게이트 #3) ------------------------ */
|
/* ---- 형태: radius 2종 (게이트 #3) ------------------------ */
|
||||||
--radius-sm: 3px; /* 버튼·입력·배지 */
|
--radius-sm: 3px; /* 버튼·입력·배지 */
|
||||||
|
|
@ -65,6 +66,7 @@
|
||||||
--accent: #3FA89D; /* 6.30:1 */
|
--accent: #3FA89D; /* 6.30:1 */
|
||||||
--accent-ink: #0B1A18; /* 6.21:1 */
|
--accent-ink: #0B1A18; /* 6.21:1 */
|
||||||
--accent-inverse: #0E6B63;
|
--accent-inverse: #0E6B63;
|
||||||
|
--accent-ink-inverse: #FFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,6 +121,7 @@
|
||||||
--ink-muted: var(--ink-muted-inverse);
|
--ink-muted: var(--ink-muted-inverse);
|
||||||
--line: var(--line-inverse);
|
--line: var(--line-inverse);
|
||||||
--accent: var(--accent-inverse);
|
--accent: var(--accent-inverse);
|
||||||
|
--accent-ink: var(--accent-ink-inverse);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
padding-block: var(--space-8);
|
padding-block: var(--space-8);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue