Communitygithub.com

RevanthThota55/FreeFrame-Studio

애플의 인터페이스 디자인 접근 방식과 유연한 물리적 모션을 웹으로 변환합니다. 제스처 기반 UI, 스프링 애니메이션, 드래그/스와이프/시트 인터랙션, 모멘텀 및 중단 가능한 전환, 반투명 소재와 깊이, 타이포그래피(광학 크기, 자간, 행간), 축소 모션, 또는 Apple 스타일 인터페이스 뒤에 있는 디자인 기초(피드백, 공간적 일관성, 절제)를 구축하거나 검토할 때 사용합니다.

FreeFrame-Studio란 무엇인가요?

이 기술은 Apple의 WWDC 디자인 강연, 특히 'Designing Fluid Interfaces'(WWDC 2018)에서 영감을 받은, 유연하고 반응성이 뛰어난 웹 인터페이스 구축 방법을 제공합니다. 핵심 원칙은 애니메이션이 현재 화면 값에서 시작하고, 사용자의 속도를 상속하며, 모멘텀을 앞으로 투영하고, 언제든지 잡아서 되돌릴 수 있어야 한다는 것입니다. 이는 CSS, Pointer Events, requestAnimationFrame, 그리고 Motion/Framer Motion과 같은 스프링 라이브러리를 사용하여 구현됩니다. 이 스킬은 응답성(지연 시간 최소화), 직접 조작(1:1 트래킹), 중단 가능성(모든 애니메이션은 도중에 변경 가능), 스프링 기반 동작, 속도 핸드오프, 모멘텀 투영, 공간적 일관성, 고무줄 효과, 머티리얼 디자인(backdrop-filter), 그리고 멀티모달 피드백(모션, 사운드, 햅틱) 등 13가지 핵심 영역을 다룹니다. Claude Code, Cursor, Codex와 같은 AI 에이전트에서 활용 가능하며, 개발자가 Apple 스타일의 직관적이고 유연한 웹 인터페이스를 구축하고 검토하는 데 도움을 줍니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/RevanthThota55/FreeFrame-Studio/tree/HEAD/.claude/skills/apple-design

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

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

문서

Apple 디자인

초기 응답

이 스킬이 특정 질문 없이 처음 호출되면, 다음 내용으로만 응답하세요:

저는 웹에서 유연한 Apple 스타일의 인터페이스를 구축할 수 있도록 도울 준비가 되었습니다. 제 지식은 Apple의 WWDC 디자인 강연을 웹 환경에 맞게 변환한 것입니다.

사용자가 질문하기 전까지는 다른 정보를 제공하지 마세요.

Apple이 인터페이스를 더 이상 컴퓨터처럼 느껴지지 않고 사용자의 연장선처럼 느껴지도록 만드는 방법입니다. 이 지식은 Apple의 WWDC 디자인 강연 — 주로 Designing Fluid Interfaces (WWDC 2018) — 에서 얻은 내용을 웹 플랫폼(CSS, Pointer Events, requestAnimationFrame, Motion/Framer Motion 같은 스프링 라이브러리)에 맞게 재해석하고 변환한 것입니다.

핵심 흐름: 인터페이스가 살아있다고 느껴지려면 모션이 현재 화면의 값에서 시작하고, 사용자의 속도를 상속받으며, 모멘텀을 앞으로 투영하고, 언제든지 잡아서 방향을 바꿀 수 있어야 합니다. 스프링은 이러한 모든 것을 자연스럽게 만들어주는 도구입니다. 스프링은 본질적으로 중단 가능하고 속도를 인지하기 때문입니다.

핵심 아이디어

"인터페이스를 우리의 사고 방식과 움직임 방식에 맞추면, 마법 같은 일이 일어납니다. 더 이상 컴퓨터처럼 느껴지지 않고, 우리의 매끄러운 연장선처럼 느껴지기 시작합니다."

인터페이스가 물리적 세계처럼 행동할 때 유연해집니다: 사물이 즉시 반응하고, 연속적으로 움직이며, 모멘텀을 전달하고, 경계에서 저항하며, 움직임 도중에 방향을 바꿀 수 있습니다. 아래의 모든 내용은 이러한 상태에 도달하기 위한 방법입니다.

Apple은 디자인이 네 가지 인간의 욕구를 충족시킨다고 봅니다: 안전/예측 가능성, 이해, 성취, 그리고 즐거움. 여기의 모든 규칙은 이 중 하나를 충족시킵니다.

1. 반응 — 지연 시간 제거

지연이 발생하는 순간, 직접 조작감은 "절벽에서 떨어집니다." 반응은 다른 모든 것이 구축되는 기초입니다.

  • 포인터를 놓을 때가 아니라, 누를 때 반응하세요. 버튼을 누르는 즉시 강조 표시하세요. click/터치 업까지 기다렸다가 피드백을 보여주면 죽은 느낌입니다.
  • 모든 지연에 주의하세요. 디바운스, 인공 타이머, 전환 대기 시간, 약 300ms의 탭 지연을 감사하세요. 입력 경로에서 필수적이지 않은 모든 것은 퇴보입니다.
  • 피드백은 상호작용 중에 연속적이어야 하며, 마지막에만 있어서는 안 됩니다. 드래그, 슬라이더 또는 서랍의 경우, 포인터와 1:1로 UI를 업데이트하세요 — 제스처가 완료될 때만 애니메이션을 적용하지 마세요.
/* 피드백은 누르는 순간에 있으며, 즉각적입니다 */
.button:active {
  transform: scale(0.97);
  transition: transform 100ms ease-out;
}

2. 직접 조작 — 1:1 추적

"터치와 콘텐츠는 함께 움직여야 합니다."

사용자가 무언가를 드래그할 때, 손가락에 붙어 있어야 하며 — 잡은 위치로부터의 오프셋을 존중해야 합니다. 잡는 순간 요소의 중앙으로 스냅하면 환상이 즉시 깨집니다.

  • Pointer Events와 setPointerCapture를 사용하여 포인터가 요소의 경계를 벗어나도 추적이 계속되도록 하세요.
  • 현재 지점뿐만 아니라 짧은 속도/위치 기록(마지막 몇 개의 pointermove 이벤트)을 추적하세요 — 릴리스 시 속도가 필요합니다.
el.addEventListener('pointerdown', (e) => {
  el.setPointerCapture(e.pointerId);
  const grabOffset = e.clientY - el.getBoundingClientRect().top; // 잡은 위치 존중
  // ...속도를 위한 위치 + 타임스탬프 기록 추적
});

3. 중단 가능성 — 가장 중요한 원칙

"생각과 제스처는 병렬로 일어납니다."

모든 애니메이션은 언제든지 중단 가능하고 방향을 바꿀 수 있어야 합니다. 사용자는 움직이는 요소를 공중에서 잡아서 애니메이션이 끝날 때까지 기다리지 않고 방향을 바꿀 수 있어야 합니다. 사용자가 다시 잡은 닫히는 모달은 손가락을 따라야 합니다 — 먼저 닫힌 다음 다시 열리면 안 됩니다.

  • 전환 중에 입력을 절대 차단하지 마세요.
  • 항상 표현(현재) 값에서 애니메이션을 시작하고, 목표 값에서 시작하지 마세요. 중단 시, 요소의 라이브 화면 변환값을 읽고 거기서부터 새 애니메이션을 시작하세요. 논리적/목표 값에서 시작하면 눈에 띄는 점프가 발생합니다.
  • 제스처 기반의 경우 CSS 전환과 @keyframes를 피하세요 — 도중에 매끄럽게 잡거나 방향을 바꿀 수 없습니다. 스프링은 기본적으로 현재 값에서 애니메이션을 시작하므로, 중단에 정확히 필요한 것입니다.
  • 제스처가 방향을 바꿀 때, 속도를 부드럽게 혼합하세요 — 급격히 자르지 마세요. 반전 시 하나의 애니메이션을 다른 것으로 대체하면 속도 불연속성, 즉 "벽돌 벽"이 발생합니다. 재타겟팅 시 속도를 유지하는 스프링 라이브러리는 이를 피합니다. (이것이 iOS의 가산 애니메이션이 기본적으로 하는 일입니다; 웹에서는 현재 속도로 재타겟팅하는 스프링 라이브러리를 선택하세요.)
  • 2D 모션을 독립적인 X 및 Y 스프링으로 분해하세요. 2D 거리에 단일 스프링을 사용하면 X와 Y의 속도가 다를 때 동기가 맞지 않습니다.

4. 동작보다는 행동 — 스프링 사용

"애니메이션을 인터페이스가 미리 정해 놓은 것이 아니라, 사용자와 객체 간의 대화라고 생각하세요."

미리 정해진 고정 시간 애니메이션은 새로운 입력에 반응할 수 없습니다. 스프링은 가능합니다 — 새로운 입력은 목표를 변경할 뿐이며, 모션은 연속적으로 유지됩니다. 사용자가 터치할 수 있는 모든 것에는 스프링을 사용하세요.

Apple은 의도적으로 물리학 삼중항(질량/강성/감쇠)을 디자이너에게 친숙한 두 매개변수로 대체했습니다. 이것들로 생각하세요:

  • 감쇠 비율 — 오버슈트를 제어합니다. 1.0 = 임계 감쇠, 바운스 없음, 부드러운 정착. < 1.0 = 오버슈트 및 진동. 낮을수록 더 튕깁니다.
  • 응답 — 값이 목표에 도달하는 속도(초 단위)입니다. 낮을수록 더 빠릅니다. 이것은 "지속 시간"이 아닙니다 — 스프링에는 고정 지속 시간이 없습니다. 정착 시간은 매개변수에 따라 결정됩니다.

기본값:

  • 대부분의 UI는 감쇠 1.0 (임계 감쇠)으로 시작하세요 — 우아하고 산만하지 않습니다.
  • 제스처 자체에 모멘텀이 있었을 때만 (튕기기, 던지기, 드래그 릴리스) 바운스(감쇠 ~0.8)를 추가하세요. 그냥 나타난 메뉴의 오버슈트는 어색하게 느껴집니다; 당신이 튕긴 카드의 오버슈트는 자연스럽게 느껴집니다.

Apple이 사용하는 구체적인 값:

상호작용감쇠응답
이동 / 재배치 (예: PiP)1.00.4
회전0.80.4
서랍 / 시트0.80.3

웹 매핑 (Motion / Framer Motion): bounce + duration 스프링 API는 Apple의 감쇠 + 응답과 밀접하게 매핑됩니다. 안전한 기본 스타일은 모든 곳에서 damping: 1.0 스프링을 기본으로 사용하고, 모멘텀 기반의 물리적 상호작용을 위해 바운스를 예약하는 것입니다.

import { animate } from 'motion';

// 임계 감쇠 기본값 (오버슈트 없음)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });

// 모멘텀 상호작용 — 약간의 바운스, 튕기기가 선행되었기 때문
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });

5. 속도 핸드오프 — 드래그와 애니메이션 사이의 이음새

제스처가 끝날 때, 애니메이션은 손가락의 정확한 속도로 계속되어야 하므로, 드래그와 애니메이션 사이에 눈에 띄는 이음새가 없어야 합니다. 이것이 "유연함"과 "괜찮음"을 가장 크게 구분짓는 세부 사항입니다.

포인터의 릴리스 속도를 스프링의 초기 속도로 전달하세요. 일부 스프링 API는 상대적인 속도를 원합니다 — 목표까지의 남은 거리로 정규화하세요:

relativeVelocity = gestureVelocity / (targetValue − currentValue)

예: 요소가 y=50에 있고, 목표가 y=150(100px 남음), 손가락이 50px/s로 움직이는 경우 → 초기 스프링 속도 = 50 / 100 = 0.5. Framer Motion / Motion은 절대 px/s 속도를 직접 받아들이므로(velocity 옵션), 일반적으로 원시 값을 전달합니다.

6. 모멘텀 투영 — 제스처가 향하는 곳으로 애니메이션

"작은 입력을 받아 큰 출력을 만드세요."

릴리스 지점에서 가장 가까운 경계로 스냅하지 마세요. 속도를 사용하여 정지 위치를 투영하세요 — 정확히 스크롤 감속처럼 — 그런 다음 투영된 지점에 가장 가까운 목표로 스냅하세요. 이것이 튕기기가 요소를 던지는 듯한 느낌을 주는 이유입니다.

Apple의 정확한 투영 함수 (Designing Fluid Interfaces 샘플 코드에서):

// decelerationRate ≈ 0.998 (일반 스크롤 느낌); 0.99 (더 빠른 느낌)
function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
  return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
}

const projectedEndpoint = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedEndpoint);   // 투영에서 목표 선택
animateSpringTo(target, { velocity: releaseVelocity }); // 그런 다음 속도 핸드오프 (§5)

참고: 물리 교과서의 v²/(2·decel) 공식은 Apple이 사용하는 방식이 아닙니다 — 위의 지수 감쇠 형태를 사용하세요. 이것은 좋은 바텀 시트와 캐러셀(Vaul, Embla)의 표준 동작입니다.

7. 공간적 일관성 — 대칭 경로, 고정된 원점

"어떤 것이 한 방향으로 사라지면, 우리는 그것이 왔던 곳에서 나타날 것으로 기대합니다."

  • 같은 경로로 들어오고 나가세요. 오른쪽에서 슬라이드되어 들어오는 패널은 오른쪽으로 사라져야 합니다. 오른쪽에서 들어와서 아래로 나가는 것은 단절감과 혼란을 줍니다.
  • 상호작용을 그 근원에 고정하세요. 메뉴, 팝오버 또는 시트는 그것을 트리거한 요소에서 시작해야 합니다 — transform-origin을 트리거로 설정하여 버튼과 콘텐츠 간의 공간적 관계를 명확하게 하세요. (이것은 팝오버가 중앙이 아닌 트리거에서 확장되는 원점 인식 지점과 동일합니다.)
  • 가역적인 전환에는 이징을 미러링하여 나가는 경로가 돌아오는 경로와 일치하도록 하세요 (두 방향에 대해 역 3차 베지어 제어점 사용).

8. 제스처 방향으로 힌트 제공

인간은 궤적을 통해 최종 상태를 예측합니다. 중간 모션은 사물이 어디로 가는지 암시해야 합니다 — 제어 센터 모듈은 "손가락을 향해 위와 밖으로 자랍니다." 중간 프레임이 결과를 가리키도록 하고, 맹목적으로 보간하지 마세요.

9. 고무줄 효과 — 부드러운 경계

경계에서, 딱 멈추는 대신 점진적으로 저항하세요. 딱 멈추는 것은 "얼어붙은" 것으로 읽힙니다; 지속적인 저항은 "반응하지만, 더 이상 여기에 아무것도 없다"는 것으로 읽힙니다. 사용자가 경계를 넘어 드래그할수록 증가하는 감쇠를 적용하세요.

// 경계를 넘을수록 요소가 덜 따라옵니다 — 실제 사물은 멈추기 전에 느려집니다
function rubberband(overshoot, dimension, constant = 0.55) {
  return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}

10. 제스처 디자인 세부 사항 ("느낌" 체크리스트)

  • 탭: 터치-다운 시 강조(즉시), 터치-업 시 실행. 대상 주위에 약 10px의 히스테리시스/히트 여백을 추가하고, 드래그하여 멀어졌다가 다시 돌아와 취소할 수 있도록 허용하세요.
  • 드래그/스와이프: 방향을 결정하기 전에 약간의 움직임 임계값(히스테리시스, ~10px)을 요구한 다음, 1:1로 추적하세요.
  • 첫 번째 움직임부터 모든 가능한 제스처를 병렬로 감지하고, 의도가 명확해지면 확실히 패자를 취소하세요. 최종 상태만 보고하는 인식기(swipeleft 유형 이벤트)는 피하세요 — 피드백에 필요한 연속 추적을 버립니다.
  • 모호성 해소 지연을 최소화하세요. 더블 탭 감지는 불가피하게 단일 탭을 지연시킵니다; 더블 탭이 실제로 존재하는 경우에만 그 비용을 지불하세요.

11. 프레임 수준의 부드러움

부드러움은 프레임 속도뿐만 아니라 프레임 안에 무엇이 있는지에 관한 것입니다.

  • 프레임당 위치 변화를 인지 임계값 아래로 유지하여 스트로빙을 방지하세요.
  • 매우 빠른 모션의 경우, 미묘한 모션 블러 / 스트레치가 속도를 인코딩하며 날카로운 선보다 더 잘 읽힙니다.
  • requestAnimationFrame은 웹의 디스플레이 동기화 시계입니다(Apple은 CADisplayLink 사용). 컴포지터 친화적인 속성인 transform과 opacity에만 애니메이션을 적용하고, 모션이 임박한 곳에 will-change 힌트를 주세요.

12. 머티리얼 및 깊이 — 반투명이 계층 구조를 전달합니다

Apple은 반투명 머티리얼을 주의를 빼앗지 않으면서 구조를 제공하는 떠 있는 기능적 레이어로 사용합니다. 웹에서는 backdrop-filter로 근사화하세요.

  • 네비게이션/툴바/시트를 반투명 레이어로 구축하세요(backdrop-filter: blur() + 반투명 배경). 콘텐츠는 그 아래로 스크롤되며, 고정된 스트립을 차지하는 불투명한 막대가 아닙니다.
  • 머티리얼 무게는 계층 구조를 인코딩합니다: 더 어둡고 무거운 머티리얼은 구조적 영역(사이드바)을 분리합니다; 더 밝은 머티리얼은 상호작용 요소(버튼)에 주의를 끕니다. 반투명 밝은 표면을 다른 위에 절대 쌓지 마세요 — 가독성이 무너집니다.
  • 더 큰 표면은 더 두껍게 읽혀야 합니다: 작은 칩보다 강한 블러와 더 깊은 그림자. 콘텐츠 인식 그림자를 고려하세요 — 복잡한/텍스트 콘텐츠 위에서는 더 무겁게(분리를 위해), 일반 배경 위에서는 더 밝게.
  • 어둡게 하여 집중시키고, 분리하여 흐름을 유지하세요. 모달 작업은 표면을 어둡게 하는 스크림과 함께 배경을 뒤로/아래로 밀어냅니다. 병렬적이고 비차단적인 패널은 스크림 없이 반투명과 오프셋을 사용하여 흐름이 끊기지 않도록 합니다. 쌓인 시트의 경우, 각 상위 레이어를 점진적으로 어둡게 하고 뒤로 밀어내세요.
  • 생생함은 변화하는 배경에서 텍스트 가독성을 유지합니다. 블러/반투명 표면 위에서는 평평한 회색 텍스트를 사용하지 마세요 — 더 높은 대비, 약간 더 무거운 두께, 그리고 약간의 자간 증가를 사용하세요. 색상은 반투명 전경이 아닌 단색 레이어에 배치하세요.
  • 하드 구분선 대신 스크롤 가장자리 효과를 사용하세요. 고정 헤더 아래에 1px 테두리를 사용하는 대신, 콘텐츠가 떠 있는 크롬과 만나는 곳에 작은 블러/그라데이션 마스크를 페이드 인하세요 — 떠 있는 UI가 실제로 콘텐츠와 겹치는 경우에만.
  • 그냥 페이드 인하지 말고, 구체화하세요. 유리/블러 표면의 경우, 들어오고 나갈 때 블러 반경과 스케일을 함께 애니메이션하여 표면이 단순한 불투명도 페이드가 아니라 실제 머티리얼이 도착하는 것으로 읽히도록 하세요.
.toolbar {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(20px) saturate(180%);
  border-top: 1px solid rgba(255, 255, 255, 0.4); /* 밝은 상단 가장자리 = 머티리얼에 닿는 빛 */
}

13. 멀티모달 피드백 — 모션 + 사운드 + 햅틱

감각을 결합하기 위한 세 가지 규칙 (Designing Audio-Haptic Experiences에서):

  1. 인과성 — 피드백의 원인이 무엇인지 명확해야 합니다. 실제 인과 이벤트(토글이 뒤집히는 순간, 아이템이 제자리에 스냅되는 순간)에서 트리거하고, 그 특성을 동작의 물리적 특성과 일치시키세요.
  2. 조화 — 시각, 사운드, 햅틱은 같은 프레임에서 실행되어야 합니다. 이들 사이의 지연은 환상을 파괴합니다.

…

(번역을 위해 잘렸습니다.)

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

관련 스킬