Communitygithub.com

Stretchy Morph UI

웹 UI를 스트레치 모프 UI로 변환합니다. 트랜지션이 이징 곡선 대신 스프링처럼 움직이며 압축, 늘어남, 오버슈트 후 안정됩니다. 사용자의 기존 코드를 감사해 스트레치가 가능한 컴포넌트를 찾고, 확인 후 편집합니다.

Stretchy Morph UI란 무엇인가요?

이 스킬은 웹 UI의 상태 전환(탭, 토글, 버튼, 모달, 드롭다운, 아코디언, 캐러셀, 토스트, 사이드바, 날짜 선택기, 장바구니 추가 등)에서 발생하는 모든 움직임을 스프링 물리학 기반의 '모프(morph)'로 대체합니다. 기존 CSS 이징 대신 스프링을 사용해 요소가 늘어나고, 압축되고, 오버슈트하며 생생한 재질감을 표현합니다. 이 스킬은 24개의 실제 동작하는 참조 컴포넌트, 스프링 엔진, 사용자 코드 스캐너, 브라우저 체커, 그리고 새로운 스트레치 컴포넌트를 설계하는 방법을 포함합니다. 사용자가 애니메이션 효과나 생동감 있는 UI, 마이크로 인터랙션, 스프링, 탄성, 바운스, 끈적임 등을 원할 때 유용합니다. 모든 스킬 파일(SKILL.md, assets, references, scripts)은 이 스킬 폴더를 기준으로 접근합니다. 사용자가 원하는 UI 컴포넌트를 찾기 위해 먼저 사용자 코드를 감사하고 확인을 받은 후에야 편집을 시작하도록 설계되어 안전하고 정확한 결과를 보장합니다.

지원 대상✓Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add https://github.com/pafelab/stretchy-morph-ui/tree/HEAD

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Stretchy Morph UI은(는) 무엇을 하나요?

이 스킬은 모든 상태 변화가 *모프(morph)*되도록 만듭니다. 탭 표시기가 다음 탭으로 가는 길에 벌레처럼 늘어나고, 버튼이 로딩 원으로 쪼그라들었다가 다시 부풀어 오르며, 제품 사진이 카드에서 떠오른 후 칩(chip) 모양으로 줄어들어 장바구니로 떨어집니다. 이 스킬은 24개의 컴포넌트 쇼케이스(assets/showcase.html)를 구축하는 과정에서 발견한 버그와 디자인 수정 사항을 바탕으로 정제되었습니다.

이러한 외관은 한 가지 아이디어에서 비롯됩니다: 늘어남은 무엇인가에 의해 발생합니다. 요소는 부품들이 동의하지 않기 때문에(한쪽 가장자리가 다른 쪽보다 먼저 도착), 빠르게 움직이기 때문에(경로를 따라 길어짐), 압축되기 때문에(부피를 유지하기 위해 반대 방향으로 부풀어 오름), 또는 충격을 받았기 때문에(흔들림) 변형됩니다. 반복적인 무작위 흔들림은 싸구려처럼 보이지만, 원인이 있는 늘어남은 재질감처럼 보입니다.

0단계, 매번: 사용자 코드 감사 및 확인

무엇을 만들거나 편집하기 전에, 사용자 인터페이스에서 스트레치(stretchy)로 만들 수 있는 부분을 찾고 사용자가 선택하도록 합니다. 세부 사항, 판단 기준 및 메시지 템플릿은 references/audit.md에 있습니다. 간략한 버전은 다음과 같습니다:

  1. 코드 찾기: 붙여넣은 코드, 첨부 파일(claude.ai에서 /mnt/user-data/uploads 목록), 작업 디렉토리나 저장소, 아티팩트 링크(아티팩트 도구로 읽기), 또는 사용자가 지정한 소스. 코드가 없으면 무엇을 찾았는지 말하고 두 가지 경로를 제시하세요: 코드를 공유하거나, 처음부터 새로 만드는 것.
  2. 스캔하기: python scripts/audit_ui.py <경로>는 스택, 기존 모션, 누락된 reduced-motion 처리, 그리고 UI 카테고리별 후보를 파일:줄 형식으로 보고하며, 각각은 쇼케이스 컴포넌트와 기법에 매핑됩니다.
  3. 모든 히트를 문맥에서 읽고 실제 후보만 남기세요. 각각에 대해 위치, 현재 동작, 적용될 모프 문장(morph sentence), 노력(소, 중, 대), 그리고 위험(컴포넌트 라이브러리 마크업, 기존 애니메이션 라이브러리, 모바일 피커가 손실될 네이티브 컨트롤, 매우 긴 목록)을 기록하세요.
  4. 우선순위를 정하세요: 가시성 × 빈도 × 적합도. 중복을 그룹화하고(모든 버튼이 하나의 공유 Button을 사용하는 경우 하나의 항목으로), 목록을 약 10개로 제한하고, 권장하지 않는 항목은 이유와 함께 나열하세요.
  5. 사용자의 언어로 확인을 요청하세요: 번호가 매겨진 제안, 그리고 번호를 선택하거나, 추천 항목을 모두 선택하거나, 목록에 없는 추가 부분을 입력하도록 초대하는 질문. 환경에 맞춰 탭 가능한 선택 도구를 사용하거나, 없으면 일반 텍스트를 사용하세요. 해당 턴을 여기서 끝내고, 답변을 받기 전에 편집하지 마세요.
  6. 사용자가 선택한 항목과 추가한 부분을 구축하고, 아래 워크플로우를 따라 각 항목에 대해 보고하세요.

사용자가 이미 원하는 부분을 명명했더라도 여전히 감사하세요: 사용자의 부분을 먼저 배치하고, 나머지는 선택 사항으로 제공하며, 질문은 짧게 유지하세요.

파일

모든 경로는 이 스킬의 폴더(이 SKILL.md가 포함된 폴더)에 상대적이며, 사용자 프로젝트에 상대적이지 않습니다. 읽거나 실행할 때 스킬 폴더를 기준으로 경로를 확인하세요.

경로설명열어야 할 때
assets/stretchy-core.js스프링 엔진 및 헬퍼(에지 쌍, 분할 표시기, 볼륨 스케일, 부풀어 오르는 SVG 에지, FLIP 재정렬, 던져진 객체)구축하는 모든 페이지에 붙여넣으세요. 헬퍼를 알기 위해 한 번 읽으세요.
assets/starter.html페이지 스켈레톤: 라이트/다크 토큰, 태국어 지원 가변 폰트, reduced-motion 처리, 인라인 코어, 하나의 예제 컴포넌트새 페이지를 시작할 때
assets/showcase.html원래 구축된 24개의 모든 동작 컴포넌트. 각 JS 섹션은 /* ---------- tabs ---------- */와 같은 배너로 시작합니다.요청이 카탈로그 컴포넌트와 일치할 때, grep -n "/\* ---------- " assets/showcase.html로 해당 섹션과 CSS를 읽으세요.
references/audit.md사용자 코드를 찾고, 스캔하고, 판단하고, 우선순위를 정하는 방법과 확인 메시지 템플릿(태국어 및 영어)0단계, 매번
references/techniques.md11가지 스트레치 기법에 대한 심층 설명, 코드 및 튜닝 수치 포함이전에 구축하지 않은 것을 만들기 전에
references/components.md카탈로그: 각 컴포넌트의 레시피, 스프링 값 및 주의사항요청이 카탈로그 컴포넌트를 명명하거나 유사할 때
references/new-components.md모든 컴포넌트의 스트레치 버전을 설계하는 방법과 쇼케이스에 없는 14개의 작업 설계 포함요청이 카탈로그 외부에 있을 때
scripts/audit_ui.py사용자 코드 스캐너: 스택, 기존 모션, 그리고 파일:줄 형식의 스트레치 후보0단계, 매번
scripts/check_page.py헤드리스 브라우저 검사: JS 오류, 휴대폰 및 데스크톱에서의 가로 오버플로우, 클릭을 가로채는 보이지 않는 요소, 애니메이션 프레임 연락처 시트모든 빌드 후, 인계 전에

시각 디자인 스킬(예: frontend-design 또는 ui-ux-pro-max)을 사용할 수 있다면 그것도 읽으세요. 해당 스킬은 페이지의 외관(팔레트, 타이포그래피, 레이아웃)을 결정하고, 이 스킬은 움직임을 결정합니다. 모션 폴리시만으로는 평범해 보이는 페이지를 구할 수 없습니다.

워크플로우 (사용자 확인 후)

  1. 각 컴포넌트에 대한 모프 문장을 작성하세요. "트리거가 발생하면, 모양 A가 모양 B가 되고, 원인 때문에 늘어납니다." 예: "탭이 클릭되면, 표시기가 그 탭으로 이동하고, 앞쪽 가장자리가 뒤쪽 가장자리보다 더 뻣뻣하기 때문에 늘어납니다." 원인을 채울 수 없으면 아래 표에서 하나를 선택하세요. 이 문장이 디자인이며, 나머지는 구현입니다.
  2. 표에서 기법을 선택하세요. 대부분의 컴포넌트는 하나의 기본 스트레치와 하나의 작은 피드백 워블(wobble)을 사용합니다.
  3. 정지 상태를 숫자로 정의하세요: 너비, 높이, 반지름, 위치, 진행 0~1. 스프링은 숫자를 애니메이션화하고 onUpdate에서 style에 씁니다. CSS 트랜지션은 색상과 불투명도(및 짧은 콘텐츠 크로스페이드)만 처리합니다.
  4. 콘텐츠를 계획하세요. 텍스트와 아이콘은 컨테이너가 모프되는 동안 리플로우되거나 찌그러져서는 안 됩니다. 각 콘텐츠 상태에 대해 최종 크기의 자체 레이어를 제공하고, 고정(보통 중앙)한 후 크로스페이드합니다: 빠르게 종료(약 150ms), 약 100-150ms 지연 후 약간의 스케일이나 블러로 진입합니다. 컨테이너의 overflow:hidden은 콘텐츠가 자라면서 드러나게 합니다.
  5. assets/starter.html에서 코어를 인라인하여 구축하세요. 스프링 콜백 내에서 직접 스타일을 작성하고, 프레임마다 프레임워크 리렌더를 트리거하지 마세요.
  6. 접근성 및 중단 처리(아래 체크리스트 참조).
  7. 테스트 및 확인. scripts/check_page.py를 실행한 다음, 중요한 인터랙션의 프레임 연락처 시트를 캡처하여 실제로 확인하세요. 원래 빌드의 장바구니 추가(add-to-cart) 애니메이션은 "작동"했지만 프레임을 검사할 때까지 싸구려처럼 보였습니다. 수정 사항은 "멋지게 만들기" 섹션에 있습니다.

늘어남의 원인

상황기법 (references/techniques.md에 세부 사항)사용 예
무언가가 슬롯 사이를 이동할 때에지 쌍(Edge pair): 앞쪽과 뒤쪽 가장자리에 각각 다른 강성의 두 스프링, 방향에 따라 앞쪽이 뻣뻣함탭, 캐러셀 점, 토글, 선택 칩, 라디오 점, 스테퍼, 드롭다운 하이라이트, 평점 캡슐, 사이드바 내비게이션
컨테이너가 크기나 모양을 변경할 때축 분할(Split axes): 너비, 높이, 반지름에 대해 다른 강성으로 별도의 스프링을 사용하여 한 축이 먼저 움직임다이나믹 아일랜드, FAB에서 폼으로, 버튼에서 모달, 드롭다운, 장바구니 추가 버튼
무언가가 빠르게 좁아지거나 넓어질 때부피 보존(Volume preservation): 다른 축을 속도로 스케일링, scaleY = clamp(1 − v/k)보내기 버튼, 검색 필드, 드롭다운 너비
무언가가 빠르게 이동할 때속도 늘어남(Velocity stretch): 이동 방향으로 길어지고, 수직으로 얇아짐슬라이더 썸, 던져진 장바구니 아이템, 캐러셀 카드(기울임), 툴팁
큰 표면이 움직일 때부풀어 오르는 가장자리(Bulging edge): 표면을 SVG 경로로 그리고, 가장자리 제어점을 -속도만큼 오프셋사이드바, 바텀 시트
한 부품이 움직이는 부품에 매달려 있을 때느린 후행자(Lagging follower): 더 부드러운 스프링이 메인 스프링을 추적하고, 간격을 기울기나 오프셋에 사용슬라이더 값 버블, 아코디언 내부 텍스트
탭 또는 도착에 대한 피드백킥(Kick): 0에서 정지한 스프링에 속도를 추가하면 흔들리다가 안정됨체크박스, 칩, 장바구니, 별, 배지, 수량 제한 흔들림
블롭(blobs)이 합쳐지거나 분리될 때구이 필터(Goo filter): 블러 + 알파 임계값, 위에 선명한 소스 합성구이 메뉴
요소가 다른 요소로 변할 때컨테이너 변환(Container transform): 트리거의 사각형에서 시작하여 스프링으로 대상 사각형으로 이동, 콘텐츠 크로스페이드모달, FAB, 장바구니 던지기
변경 사이에 일시 중지가 필요할 때의미를 전달하는 중간 모양을 통한 순차적 모프(Sequenced morph) (스피너, 체크)보내기 버튼, 장바구니 추가 버튼
한계를 넘어 드래그하거나 목록이 재정렬될 때고무 밴드(Rubber band) (오버스크롤을 약 3으로 나눔) 및 속도를 유지하는 스프링을 사용한 FLIP캐러셀, 시트, 정렬된 제품 목록

엔진을 한 문단으로 요약

Spring은 감쇠 조화 진동자로, 프레임당 8개의 서브스텝으로 반암시적 오일러(semi-implicit Euler)를 사용하여 적분합니다(프레임 델타는 1/30초로 제한되어 백그라운드 탭에서 폭발하지 않도록 함). to(target)은 속도를 재설정하지 않고 대상을 다시 지정하므로, 빠른 클릭이 재시작되는 대신 연속적으로 느껴집니다. set(x)은 애니메이션 없이 즉시 점프하고, drag(x, v)는 속도를 기록하면서 포인터를 따라가며, kick(v)는 임펄스를 주입합니다. 감쇠는 모든 틱에서 전역 bounce 계수로 곱해지므로, 하나의 페이지 수준 제어(부드러움 1.8, 바운시 1, 스트레치 0.5)가 모든 컴포넌트를 실시간으로 재조정합니다. prefers-reduced-motion: reduce 아래에서는 to()가 대상을 즉시 스냅하고 onRest를 즉시 실행하여, 애니메이션을 기다리는 로직이 계속 실행됩니다.

감쇠 비율 ζ = c / (2√k). UI 상태 변경은 ζ 약 0.6–0.8에서 적절하게 느껴지고, 0.3–0.5에서 장난기 있는 피드백; 1 이상에서는 오버슈트가 없습니다. 쇼케이스에서 사용된 사전 설정:

역할kc비고
에지 쌍의 앞쪽 가장자리420–52030–34먼저 도착
뒤쪽 가장자리140–17017–19지연되어 늘어남 생성
컨테이너 너비(모프)240–32018–24
컨테이너 높이(모프)150–17015–17너비보다 느려서 모양이 먼저 옆으로 늘어남
포인터 팔로워(슬라이더)65042타이트하지만 여전히 읽을 속도 있음
느린 후행자(버블)15011일부러 느슨함
킥 피드백380–42012–166–10의 킥과 함께
가시적인 오버슈트가 있는 높이(아코디언)21014

경험 법칙

  • 모든 변형을 제한하세요. 컨트롤: 부피 보존은 ±7–15% 이내. 작고 빠른 것(던져진 칩, 슬라이더 썸): 최대 35–70% 늘어남. 항상 크기를 양의 최소값으로 고정하세요. 감쇠가 부족한 스프링은 0 아래로 언더슈트할 수 있기 때문입니다.
  • 방향에 따라 강성을 교체하세요. 에지 쌍에서 이동 방향을 향한 가장자리가 뻣뻣한 스프링을 얻습니다. 교체하지 않으면 표시기가 한 방향으로는 올바르게 늘어나고 다른 방향으로는 압축됩니다.
  • 돌아올 때는 반대 타이밍을 사용하세요. 열 때 너비가 높이를 리드했다면, 닫을 때는 높이가 너비를 리드하게 하세요. 그러면 모양이 다른 경로를 따라 되돌아가며, 되감기 대신 탄성처럼 읽힙니다.
  • 상태 변경에는 스프링을, 경로 모양이 중요한 경우에는 시간 기반 곡선을 사용하세요. 던져진 객체는 예측 가능한 호(2차 베지어, ease-in-out 진행, 약 800ms)가 필요합니다. 독립적인 x/y 스프링은 방황하는 경로를 제공합니다. 어떤 경우든 프레임 간 속도에서 늘어남을 도출하세요.
  • 콘텐츠를 똑바로 유지하세요. 그림이나 텍스트를 회전시키지 않고 임의 방향으로 늘리려면 rotate(a) scale(1+s, 1−s·0.45) rotate(−a)를 사용하세요.
  • 컴포넌트당 하나의 대담한 모션만 사용하세요. 나머지 컴포넌트는 조용하게 유지: 색상 변경, 짧은 페이드.
  • 유휴 상태에서는 조용하게. 아무것도 스스로 흔들리지 않습니다. 최대 페이지 로드 시 하나의 조정된 순간(쇼케이스는 900ms 후에 아일랜드를 한 번 엽니다).

멋지게 만들기 (빌드에서 얻은 교훈)

첫 번째 장바구니 추가(add-to-cart) 버전은 단색의 둥근 공을 두 개의 독립적인 스프링을 따라 날리고, 버튼을 빈 보라색 타원으로 줄인 후 장바구니를 찌그러뜨렸습니다. 사용자가 예쁘지 않다고 말했습니다. 이를 해결한 방법은 일반화됩니다:

  • 실제 물건을 움직이세요. 제품 사진을 복제하고, 약간 그림자와 함께 들어 올린 다음, 날아가는 동안에도 사진을 보여주는 둥근 칩으로 줄이세요. 추상적인 점은 디버그 출력처럼 읽힙니다.
  • 중간 모양은 의미를 전달해야 합니다. 축소된 버튼은 스피너, 스스로 그려지는 체크, 카운트 등을 보여주지 않으면 빈 블롭에 불과합니다. 추가 버튼은 이제 체크가 있는 녹색 원이 되었다가, − n + 스테퍼로 자랍니다.
  • 컨트롤에 미묘한 볼륨을 주세요. 버튼에 ±18% 수직 부풀음은 알약 모양의 계란처럼 보였습니다. ±7%는 부드러운 재질로 읽힙니다.
  • 도착에는 수신자가 필요합니다. 장바구니 아이콘이 내려가고 기울어지며, 링이 맥동하고, 배지가 튀어나옵니다. 무언가가 던져진 것을 시각적으로 잡아야 합니다.
  • 상태 표시를 자르지 마세요. 모핑되는 overflow:hidden 알약 내부의 배지는 잘립니다. 클립 외부의 래퍼에 넣으세요.
  • 정보를 반복하지 마세요. 장바구니 알약은 "3 ชิ้น 195 บาท"을 배지 옆에 표시했습니다. 이제 총액만 표시합니다.
  • 고정 종횡비로 일러스트레이션을 그리세요. 타일 내부의 백분율 위치 CSS 모양은 타일이 넓어질 때 늘어납니다. viewBox가 있는 인라인 SVG를 사용하여 타일 중앙에 배치하세요.

접근성 및 견고성 체크리스트

  • Reduced motion을 존중하세요(코어가 처리함; CSS 키프레임과 트랜지션도 prefers-reduced-motion 미디어 쿼리에서 차단되었는지 확인).
  • 실제 button, input, role="switch", role="tab", role="slider" 등을 올바른 aria-* 상태와 함께 사용하고, 애니메이션을 시작하는 동일한 호출에서 업데이트하세요.
  • 모프에 의해 숨겨진 콘텐츠는 inert를 받습니다(클릭을 가로채지 않도록 보이지 않는 레이어에 pointer-events:none을 주고, 보이는 상태와 함께 다시 켜세요).
  • 포커스된 컨트롤이 모프로 사라질 때 의도적으로 포커스를 이동하세요: 대체 컨트롤로, 다이얼로그가 닫힌 후 트리거로, 그리고 요소가 다시 보일 때만(보이지 않는 요소는 포커스를 받을 수 없음).
  • 모바일에서 position: fixed 내의 스프링 애니메이션이 주소 표시줄 축소로 인해 깨지지 않는지 확인하세요. 필요시 dvh 단위를 사용하세요.
  • 페이지가 JS 없이 로드될 경우를 대비해 스프링이 설정되기 전까지 요소를 opacity:0으로 유지하는 초기 스타일 스니펫을 포함하세요.

관련 스킬