From 36e2b7f7d4772529bfc73ab593b1111a7ab137ef Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:53:04 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20skip-link=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 4 ++++ CHANGELOG.md | 1 + styles.css | 8 +++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 65f2457..1dff673 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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:** `top`과 같은 레이아웃 속성을 애니메이션하면 매 프레임마다 레이아웃 재계산(reflow)이 발생하여 메인 스레드를 블로킹하고 레이아웃 스래싱(layout thrashing)을 유발합니다. +**Action:** 항상 `transform`(예: `translateY`)과 같은 컴포지터 전용 속성을 애니메이션에 사용하여 메인 스레드 비용을 0ms로 만들고 부드러운 GPU 가속 렌더링을 보장해야 합니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb1089..65aed7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## [Unreleased] +- **성능 개선**: `.skip-link`의 애니메이션 속성을 `top`에서 `transform: translateY()`로 변경하여 레이아웃 재계산(reflow)으로 인한 메인 스레드 블로킹을 방지하고 부드러운 GPU 가속 렌더링을 적용했습니다. - **렌더링 힌트 정합성**: 첫 화면의 eager 이미지와 단일 LCP 후보에서 강제 `decoding="async"`를 제거해 HTML 표준의 기본 `auto` 판단에 맡기고, 지연 로드 이미지에는 비동기 디코딩 힌트를 유지했습니다. 정적 테스트가 eager, lazy, LCP 후보 집합의 존재와 조합을 검증하며, 실제 LCP 효과는 배포 후 실측 대상으로 유지합니다. - **UX/접근성 개선**: 프로젝트 카드의 클릭 영역을 카드 전체로 확장하여 사용자 편의성을 높였습니다. 태그를 확장하는 대신 가상 요소(pseudo-element) 겹침 방식을 사용하여 스크린 리더 접근성을 유지했습니다. - **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy를 적용해 XSS 방어를 강화했습니다. diff --git a/styles.css b/styles.css index e3de699..f7697d8 100644 --- a/styles.css +++ b/styles.css @@ -818,19 +818,21 @@ main:focus-visible { .skip-link { position: absolute; - top: -40px; + top: 0; 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) { From 6f9d4171ae278a83940c13dc8dda4399ca67590f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 02:02:08 +0900 Subject: [PATCH 2/3] test: guard skip-link transform animation --- tests/test_styles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_styles.py b/tests/test_styles.py index 916bc01..8457d3d 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -102,3 +102,12 @@ 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 + assert "transition: transform" in rule + assert "transition: top" not in rule From f6cd0417b44d101e5c085d9c8f59d88a22c1bfe6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 10 Aug 2026 02:21:52 +0900 Subject: [PATCH 3/3] test: harden skip-link animation contract --- .jules/bolt.md | 4 ++-- CHANGELOG.md | 2 +- tests/test_styles.py | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 1dff673..11eed41 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -18,5 +18,5 @@ **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:** `top`과 같은 레이아웃 속성을 애니메이션하면 매 프레임마다 레이아웃 재계산(reflow)이 발생하여 메인 스레드를 블로킹하고 레이아웃 스래싱(layout thrashing)을 유발합니다. -**Action:** 항상 `transform`(예: `translateY`)과 같은 컴포지터 전용 속성을 애니메이션에 사용하여 메인 스레드 비용을 0ms로 만들고 부드러운 GPU 가속 렌더링을 보장해야 합니다. +**Learning:** 이 skip-link 전환을 `top`에서 `transform`으로 바꾸면 애니메이션 중 레이아웃 재계산을 피하는 데 유리합니다. 개발자 도구에서 이 전환의 Layout 이벤트가 관찰되지 않았지만, 브라우저·장치별 GPU 가속이나 메인 스레드 비용 0ms를 보장하지는 않습니다. +**Action:** 레이아웃 속성 대신 `transform` 전환을 우선 검토하고, 성능 효과는 브라우저별 측정으로 확인하며 절대적인 GPU·비용 보장으로 기록하지 않습니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 65aed7f..2144e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # CHANGELOG ## [Unreleased] -- **성능 개선**: `.skip-link`의 애니메이션 속성을 `top`에서 `transform: translateY()`로 변경하여 레이아웃 재계산(reflow)으로 인한 메인 스레드 블로킹을 방지하고 부드러운 GPU 가속 렌더링을 적용했습니다. +- **성능 개선**: `.skip-link` 애니메이션을 `top`에서 `transform: translateY()`로 변경하여 전환 중 레이아웃 재계산을 줄일 수 있도록 했습니다. 실제 효과는 브라우저별 측정 대상입니다. - **렌더링 힌트 정합성**: 첫 화면의 eager 이미지와 단일 LCP 후보에서 강제 `decoding="async"`를 제거해 HTML 표준의 기본 `auto` 판단에 맡기고, 지연 로드 이미지에는 비동기 디코딩 힌트를 유지했습니다. 정적 테스트가 eager, lazy, LCP 후보 집합의 존재와 조합을 검증하며, 실제 LCP 효과는 배포 후 실측 대상으로 유지합니다. - **UX/접근성 개선**: 프로젝트 카드의 클릭 영역을 카드 전체로 확장하여 사용자 편의성을 높였습니다. 태그를 확장하는 대신 가상 요소(pseudo-element) 겹침 방식을 사용하여 스크린 리더 접근성을 유지했습니다. - **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy를 적용해 XSS 방어를 강화했습니다. diff --git a/tests/test_styles.py b/tests/test_styles.py index 8457d3d..7a099da 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -109,5 +109,10 @@ def test_skip_link_animates_transform_not_top() -> None: rule = _rule(".skip-link") assert "transform: translateY(-100%);" in rule - assert "transition: transform" in rule - assert "transition: top" not 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