diff --git a/.Jules/palette.md b/.Jules/palette.md index 7fb6a23..e7f216e 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -17,3 +17,6 @@ ## 2024-07-10 - prefers-reduced-motion 지원 추가 **Learning:** 시스템 레벨에서 애니메이션 줄이기(prefers-reduced-motion)를 설정한 사용자를 위해 과도한 애니메이션과 부드러운 스크롤을 비활성화하는 것이 필요합니다. 이때 `0s` 대신 `0.01ms`를 사용하여 `transitionend`와 같은 브라우저 이벤트가 정상적으로 발생하도록 해야 자바스크립트 콜백이 멈추는(hanging) 문제를 방지할 수 있습니다. **Action:** 항상 `styles.css` 하단에 `prefers-reduced-motion: reduce` 미디어 쿼리를 추가하여 모든 요소의 `animation-duration`과 `transition-duration`을 `0.01ms`로 설정하고 `scroll-behavior: auto`를 적용합니다. +## 2025-01-15 - 터치 피드백이 있는 버튼(Tactile Button Feedback) +**Learning:** 사용자는 평면적인 웹 버튼을 클릭할 때 물리적 피드백의 부재를 느낍니다. `:hover` 상태가 의도를 보여주기는 하지만, 클릭 즉시 일어나는 물리적인 반응(예: `transform: scale()`)은 인터페이스를 훨씬 더 빠릿빠릿하고 '앱 같은' 느낌을 줍니다. 적은 코드로도 큰 효과를 내는 마이크로 인터랙션입니다. +**Action:** 버튼의 `:active` 상태에 전역적으로 `transform: scale()`을 적용하여 시각적 터치 피드백을 제공합니다. diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ad628..67eb459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## [Unreleased] +- **UX 개선**: 평면적인 버튼에 클릭 시 즉각적인 물리적 피드백(Transform 축소 효과)을 제공하여 '앱 같은' 반응형 인터페이스를 구축했습니다. - **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy를 적용해 XSS 방어를 강화했습니다. - **성능 회귀 복원**: 오프스크린 `.section` 렌더링을 `content-visibility: auto`로 지연하고, 일반 섹션은 600px·콘텐츠가 큰 DIKW/projects 섹션은 1000px의 `contain-intrinsic-size` placeholder를 유지해 초기 렌더링 비용과 스크롤바 이동을 함께 줄였습니다. - **보안 개선**: Trusted Types 기반 CSP 강화: 잠재적인 DOM 기반 XSS 공격을 방지하기 위해 `require-trusted-types-for 'script'` 지시어 추가 diff --git a/styles.css b/styles.css index 02cfeb6..256e642 100644 --- a/styles.css +++ b/styles.css @@ -102,13 +102,18 @@ a { min-width: 38px; min-height: 32px; border: 0; + border-radius: 4px; background: transparent; color: var(--muted); font: inherit; font-size: 13px; font-weight: 850; cursor: pointer; - transition: opacity 0.2s; + transition: opacity 0.2s, background-color 0.2s, color 0.2s, transform 0.1s ease-out; +} + +.language-switch button:active { + transform: scale(0.94); } .language-switch button:not([aria-pressed="true"]):hover { @@ -218,13 +223,17 @@ h1 { text-decoration: none; font-size: 15px; font-weight: 800; - transition: opacity 0.2s; + transition: opacity 0.2s, transform 0.1s ease-out; } .button:hover { opacity: 0.8; } +.button:active { + transform: scale(0.97); +} + .button.primary { background: var(--ink); color: var(--white); diff --git a/tests/test_styles.py b/tests/test_styles.py index 40e8fab..1752843 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -50,3 +50,12 @@ def test_images_decode_without_blocking_rendering(): assert parser.images assert all(image.get("decoding") == "async" for image in parser.images) + + +def test_interactive_elements_have_tactile_active_states(): + """Buttons and interactive elements scale down slightly on active state.""" + button_active = _rule(".button:active") + lang_button_active = _rule(".language-switch button:active") + + assert "transform: scale(0.97);" in button_active + assert "transform: scale(0.94);" in lang_button_active