Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
## 2026-08-05 - Separate image fetch and decode hints
**Learning:** The HTML Standard defines `decoding` as a preference hint whose missing-value default is `auto`; it does not guarantee a particular main-thread or background-thread execution path. `fetchpriority` independently influences fetch priority. Removing `decoding="async"` must therefore not be described as a guaranteed LCP improvement.
**Action:** Let eager first-viewport images use the user agent's `auto` decode strategy, retain `decoding="async"` for explicitly lazy images, and require non-vacuous tests for eager, lazy, and single high-priority LCP-candidate sets. Record the evidence and measurement limits in `docs/doctoring/image-rendering-hints.md`.

## 2026-08-08 - 애니메이션 성능을 위해 top/left 대신 transform 사용
**Learning:** 이 skip-link 전환을 `top`에서 `transform`으로 바꾸면 애니메이션 중 레이아웃 재계산을 피하는 데 유리합니다. 개발자 도구에서 이 전환의 Layout 이벤트가 관찰되지 않았지만, 브라우저·장치별 GPU 가속이나 메인 스레드 비용 0ms를 보장하지는 않습니다.
**Action:** 레이아웃 속성 대신 `transform` 전환을 우선 검토하고, 성능 효과는 브라우저별 측정으로 확인하며 절대적인 GPU·비용 보장으로 기록하지 않습니다.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CHANGELOG

## [Unreleased]
- **성능 개선**: `.skip-link` 애니메이션을 `top`에서 `transform: translateY()`로 변경하여 전환 중 레이아웃 재계산을 줄일 수 있도록 했습니다. 실제 효과는 브라우저별 측정 대상입니다.
- **렌더링 힌트 정합성**: 첫 화면의 eager 이미지와 단일 LCP 후보에서 강제 `decoding="async"`를 제거해 HTML 표준의 기본 `auto` 판단에 맡기고, 지연 로드 이미지에는 비동기 디코딩 힌트를 유지했습니다. 정적 테스트가 eager, lazy, LCP 후보 집합의 존재와 조합을 검증하며, 실제 LCP 효과는 배포 후 실측 대상으로 유지합니다.
- **UX/접근성 개선**: 프로젝트 카드의 클릭 영역을 카드 전체로 확장하여 사용자 편의성을 높였습니다. <a> 태그를 확장하는 대신 가상 요소(pseudo-element) 겹침 방식을 사용하여 스크린 리더 접근성을 유지했습니다.
- **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy를 적용해 XSS 방어를 강화했습니다.
Expand Down
8 changes: 5 additions & 3 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -818,19 +818,21 @@ main:focus-visible {

.skip-link {
position: absolute;
top: -40px;
top: 0;
Comment thread
seonghobae marked this conversation as resolved.
left: 0;
transform: translateY(-100%);
background: var(--ink);
color: var(--white);
padding: 8px 16px;
z-index: 100;
font-weight: 800;
text-decoration: none;
transition: top 0.2s ease-in-out;
/* ⚡ Bolt: Animate transform instead of top to prevent layout thrashing */
transition: transform 0.2s ease-in-out;
}

.skip-link:focus-visible {
top: 0;
transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
Expand Down
14 changes: 14 additions & 0 deletions tests/test_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,17 @@ def test_project_cards_are_fully_clickable_via_pseudo_element() -> None:
after_rule = _rule(".project-grid h3 a::after")
assert "position: absolute;" in after_rule
assert "inset: 0;" in after_rule


def test_skip_link_animates_transform_not_top() -> None:
"""Skip link must animate the compositor-only transform, never top."""
rule = _rule(".skip-link")

assert "transform: translateY(-100%);" in rule
transition = re.search(r"(?m)^\s*transition\s*:\s*([^;]+);", rule)
assert transition is not None
assert "transform" in transition.group(1)
assert "top" not in transition.group(1)

focus_rule = _rule(".skip-link:focus-visible")
assert "transform: translateY(0);" in focus_rule
Loading