docs: Sync documentation from private repository
This commit is contained in:
parent
efd2889fb6
commit
4ab31b1d0a
@ -526,7 +526,7 @@
|
|||||||
| **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)<br>🆕 **SNS 공유 미리보기** (카카오톡/페이스북)<br>🆕 **서버 데이터 로딩** (Firebase Admin SDK)<br>제목, 내용, 생성된 이미지 표시<br>주제 및 팀 정보 Badge<br>프롬프트 Collapsible<br>🆕 **CommentList** (Client Component)<br>🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 |
|
| **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)<br>🆕 **SNS 공유 미리보기** (카카오톡/페이스북)<br>🆕 **서버 데이터 로딩** (Firebase Admin SDK)<br>제목, 내용, 생성된 이미지 표시<br>주제 및 팀 정보 Badge<br>프롬프트 Collapsible<br>🆕 **CommentList** (Client Component)<br>🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 |
|
||||||
| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **글 수정 기능 (URL params ?id=xxx)**<br>🆕 **수정 모드 배지 표시**<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)<br>🆕 **GenerateImageDialog 제거** (새 플로우로 대체)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 |
|
| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **글 수정 기능 (URL params ?id=xxx)**<br>🆕 **수정 모드 배지 표시**<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)<br>🆕 **GenerateImageDialog 제거** (새 플로우로 대체)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 |
|
||||||
| **이미지 업로드/선택** | `/[locale]/imageUpload` | 🆕 **이미지 소스 선택 페이지 (글 저장 후 자동 이동)** | 🆕 **2가지 선택지**: AI 생성 (장면 추출 + 선택 + 생성) / 직접 업로드<br>🆕 **드래그앤드롭 파일 업로드** (미리보기, 5MB 제한, JPEG/PNG/WebP)<br>🆕 **Canvas API 클라이언트 사이드 리사이즈** (1920x1080 최대, 85% 품질)<br>🆕 **Firebase Storage 업로드** (WritingManager.uploadUserImage)<br>🆕 **이미 이미지 있으면 자동으로 `/interaction` 리다이렉트**<br>🆕 **다국어 지원** (imageUpload namespace, ko/en/ja) | ✅ 완료 |
|
| **이미지 업로드/선택** | `/[locale]/imageUpload` | 🆕 **이미지 소스 선택 페이지 (글 저장 후 자동 이동)** | 🆕 **2가지 선택지**: AI 생성 (장면 추출 + 선택 + 생성) / 직접 업로드<br>🆕 **드래그앤드롭 파일 업로드** (미리보기, 5MB 제한, JPEG/PNG/WebP)<br>🆕 **Canvas API 클라이언트 사이드 리사이즈** (1920x1080 최대, 85% 품질)<br>🆕 **Firebase Storage 업로드** (WritingManager.uploadUserImage)<br>🆕 **이미 이미지 있으면 자동으로 `/interaction` 리다이렉트**<br>🆕 **다국어 지원** (imageUpload namespace, ko/en/ja) | ✅ 완료 |
|
||||||
| **인터랙션 편집** | `/[locale]/interaction` | 🆕 **이미지 왜곡 편집 페이지 (이미지 생성/업로드 후 이동)** | 🆕 **이미지 없으면 `/imageUpload` 자동 리다이렉트**<br>왜곡 영역 편집 (EditorCanvas, DistortionArea)<br>모션 프리셋 선택 (horizontal, vertical, rotate, pulse 등)<br>물리 설정 (stiffness, damping, mass)<br>에디터/인터랙션 모드 전환 (Switch)<br>저장 시 Writing.distortionAreas 업데이트 | ✅ 완료 |
|
| **인터랙션 편집** | `/[locale]/interaction` | 🆕 **이미지 왜곡 편집 페이지 (이미지 생성/업로드 후 이동)** | 🆕 **7:3 그리드 레이아웃** (이미지 70%, 컨트롤러 30%)<br>🆕 **Sticky Header** (InteractionHeader, 스크롤 시 상단 고정)<br>🆕 **framer-motion 애니메이션** (모드 전환, 영역 선택 부드러운 전환)<br>🆕 **영역 목록 일반 모드 표시** (CustomAreaList, 모든 모드에서 접근 가능)<br>🆕 **에디터 버튼 우측 배치** (추가/삭제/숨기기, 가로 3개)<br>🆕 **컨트롤러 내부 스크롤** (커스텀 스크롤바, 높이 80dvh)<br>🆕 **반응형 이미지** (aspectRatio 유지, 찌그러짐 해결)<br>왜곡 영역 편집 (EditorCanvas, DistortionArea)<br>모션 프리셋 선택 (horizontal, vertical, rotate, pulse 등)<br>물리 설정 (stiffness, damping, mass)<br>에디터/인터랙션 모드 전환 (Switch)<br>저장 시 Writing.distortionAreas 업데이트 | ✅ 완료 |
|
||||||
| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 | 🔜 예정 |
|
| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 | 🔜 예정 |
|
||||||
| **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)<br>🆕 **TeamCard 그리드** (글래스모피즘)<br>🆕 **페이지네이션** (커서 기반)<br>🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 |
|
| **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)<br>🆕 **TeamCard 그리드** (글래스모피즘)<br>🆕 **페이지네이션** (커서 기반)<br>🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 |
|
||||||
| **내 팀 목록** | `/[locale]/team` | 내가 만든/참여한 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 | ✅ 완료 |
|
| **내 팀 목록** | `/[locale]/team` | 내가 만든/참여한 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 | ✅ 완료 |
|
||||||
@ -770,10 +770,19 @@
|
|||||||
|
|
||||||
| 컴포넌트 | 파일명 | 설명 | 상태 |
|
| 컴포넌트 | 파일명 | 설명 | 상태 |
|
||||||
|---------|--------|------|------|
|
|---------|--------|------|------|
|
||||||
|
| **InteractionHeader** | `InteractionHeader.tsx` | 🆕 **인터랙션 페이지 Sticky Header** (IntersectionObserver + Sentinel 패턴, glassmorphism, 반응형 제목/버튼) | ✅ 완료 |
|
||||||
| **AnalysisNeededBanner** | `AnalysisNeededBanner.tsx` | 분석 필요 알림 배너 (AI 분석 요청 유도) | ✅ 완료 |
|
| **AnalysisNeededBanner** | `AnalysisNeededBanner.tsx` | 분석 필요 알림 배너 (AI 분석 요청 유도) | ✅ 완료 |
|
||||||
| **AreaUnlockBadge** | `AreaUnlockBadge.tsx` | 영역 잠금 해제 배지 (점수 달성 시 표시) | ✅ 완료 |
|
| **AreaUnlockBadge** | `AreaUnlockBadge.tsx` | 영역 잠금 해제 배지 (점수 달성 시 표시) | ✅ 완료 |
|
||||||
| **ImprovementHint** | `ImprovementHint.tsx` | 개선 힌트 표시 (점수가 낮을 때 조언 제공) | ✅ 완료 |
|
| **ImprovementHint** | `ImprovementHint.tsx` | 개선 힌트 표시 (점수가 낮을 때 조언 제공) | ✅ 완료 |
|
||||||
| **ScoreBadge** | `ScoreBadge.tsx` | 점수 배지 (오감/감정/대화/의성어 점수 표시) | ✅ 완료 |
|
| **ScoreBadge** | `ScoreBadge.tsx` | 점수 배지 (오감/감정/대화/의성어 점수 표시) | ✅ 완료 |
|
||||||
|
| **EditorModeSwitch** | `EditorModeSwitch.tsx` | 기본/고급 모드 전환 스위치 | ✅ 완료 |
|
||||||
|
| **SimpleMotionSelector** | `SimpleMotionSelector.tsx` | 간소화된 모션 선택기 (기본 모드용) | ✅ 완료 |
|
||||||
|
| **SimpleEasingSelector** | `SimpleEasingSelector.tsx` | 간소화된 이징 선택기 | ✅ 완료 |
|
||||||
|
| **SimpleSpeedSelector** | `SimpleSpeedSelector.tsx` | 간소화된 속도 선택기 | ✅ 완료 |
|
||||||
|
| **SimpleStrengthSelector** | `SimpleStrengthSelector.tsx` | 간소화된 강도 선택기 | ✅ 완료 |
|
||||||
|
| **PhysicsPresetSelector** | `PhysicsPresetSelector.tsx` | 물리 프리셋 선택기 | ✅ 완료 |
|
||||||
|
| **CustomAreaList** | `CustomAreaList.tsx` | 🆕 **왜곡 영역 목록 (일반/고급 모드 공통 표시)** | ✅ 완료 |
|
||||||
|
| **CustomParameterPanel** | `CustomParameterPanel.tsx` | 커스텀 파라미터 패널 (고급 모드 전용) | ✅ 완료 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# 라온누리 - 개발 로드맵
|
# 라온누리 - 개발 로드맵
|
||||||
|
|
||||||
> 최종 업데이트: 2025-12-23 (이미지 4:3 비율 표준화)
|
> 최종 업데이트: 2025-12-24 (인터랙션 페이지 UX 개선)
|
||||||
|
|
||||||
초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획
|
초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획
|
||||||
|
|
||||||
@ -160,6 +160,7 @@
|
|||||||
| **글 상세 페이지 Sticky Header** | **WritingDetailHeader.tsx 컴포넌트 추가, 스크롤 시 상단에 고정되는 헤더 기능, 글 제목/작성자 정보/이미지 포함 여부 표시, WritingOwnerActions 컴포넌트 재활용, 기존 헤더 관련 코드 제거 및 구조 변경** | **2025-12-19** |
|
| **글 상세 페이지 Sticky Header** | **WritingDetailHeader.tsx 컴포넌트 추가, 스크롤 시 상단에 고정되는 헤더 기능, 글 제목/작성자 정보/이미지 포함 여부 표시, WritingOwnerActions 컴포넌트 재활용, 기존 헤더 관련 코드 제거 및 구조 변경** | **2025-12-19** |
|
||||||
| **AI 이미지 생성 에러 처리 개선** | **에러 코드별 토스트 메시지 처리 로직 개선, plan_not_supported/limit_exceeded/ai_disabled 등 에러 코드 메시지 추가, 에러 발생 시 'selecting' 단계로 되돌아가는 로직 유지, 다국어 지원 (ko/en/ja)** | **2025-12-19** |
|
| **AI 이미지 생성 에러 처리 개선** | **에러 코드별 토스트 메시지 처리 로직 개선, plan_not_supported/limit_exceeded/ai_disabled 등 에러 코드 메시지 추가, 에러 발생 시 'selecting' 단계로 되돌아가는 로직 유지, 다국어 지원 (ko/en/ja)** | **2025-12-19** |
|
||||||
| **의존성 업데이트** | **@ark-ui/react 및 @zag-js 패키지 버전 업데이트 (v1.29.1 → v1.31.1)** | **2025-12-19** |
|
| **의존성 업데이트** | **@ark-ui/react 및 @zag-js 패키지 버전 업데이트 (v1.29.1 → v1.31.1)** | **2025-12-19** |
|
||||||
|
| **인터랙션 페이지 UX 대폭 개선** | **7:3 그리드 레이아웃 (이미지 70%, 컨트롤러 30%), 한 화면에서 이미지 보면서 편집 가능, 컨트롤러 내부 스크롤 (커스텀 스크롤바), InteractionHeader 컴포넌트 (IntersectionObserver + Sentinel 패턴, sticky header, glassmorphism 효과, 제목 크기 동적 변경 2xl→xl, 버튼 텍스트 숨김), framer-motion 애니메이션 (AnimatePresence, MotionBox/MotionVStack, 모드 전환/영역 선택 부드러운 전환, 0.2~0.3초 ease curve), 영역 목록을 일반 모드에서도 표시 (CustomAreaList, 고급 모드에서만 CustomParameterPanel), 에디터 버튼 우측 패널 상단 이동 (추가/삭제/숨기기, 가로 배치 flex:1), Container maxW 확장 (1400px→95vw), 이미지 반응형 수정 (InteractiveImageViewer width:100%, VStack maxH 제거, aspectRatio 충돌 해결), 높이 제한 80dvh (우측만 스크롤), 타입 체크 통과** | **2025-12-24** |
|
||||||
|
|
||||||
### 🚧 진행 중
|
### 🚧 진행 중
|
||||||
|
|
||||||
|
|||||||
260
TECH_STACK.md
260
TECH_STACK.md
@ -1,6 +1,6 @@
|
|||||||
# 라온누리 - 기술 스택 및 개발 환경
|
# 라온누리 - 기술 스택 및 개발 환경
|
||||||
|
|
||||||
> 최종 업데이트: 2025-12-19 (Curriculum Fork Model + 의존성 업데이트)
|
> 최종 업데이트: 2025-12-23 (이미지 4:3 비율 표준화)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -27,6 +27,13 @@
|
|||||||
| **Tiptap** | v3.9.1 | 리치 텍스트 에디터 |
|
| **Tiptap** | v3.9.1 | 리치 텍스트 에디터 |
|
||||||
| **next-intl** | v4.5.2 | 🆕 **다국어 지원 (i18n)** |
|
| **next-intl** | v4.5.2 | 🆕 **다국어 지원 (i18n)** |
|
||||||
|
|
||||||
|
### Image Processing
|
||||||
|
|
||||||
|
| 기술 | 버전 | 용도 |
|
||||||
|
|-----|------|------|
|
||||||
|
| **react-cropper** | ^2.3.3 | 🆕 **이미지 크롭 React 래퍼** (4:3 비율 크롭) |
|
||||||
|
| **cropperjs** | ^1.6.1 | 🆕 **이미지 크롭 라이브러리** (회전, 확대/축소, 리셋) |
|
||||||
|
|
||||||
### Backend & Database
|
### Backend & Database
|
||||||
|
|
||||||
| 기술 | 버전 | 용도 |
|
| 기술 | 버전 | 용도 |
|
||||||
@ -2546,6 +2553,257 @@ src/hooks/useShakeAnimation.ts (+45줄) - Shake 애니메이션 훅
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 25. 인터랙션 페이지 UX 패턴 (Sticky Header + 7:3 그리드 + 애니메이션)
|
||||||
|
|
||||||
|
#### 핵심 개념
|
||||||
|
|
||||||
|
**목적**: 이미지를 보면서 실시간으로 인터랙션을 편집할 수 있는 최적화된 레이아웃
|
||||||
|
|
||||||
|
#### Sticky Header 패턴 (IntersectionObserver + Sentinel)
|
||||||
|
|
||||||
|
**기술**: `WritingDetailHeader.tsx`와 동일한 패턴 재사용
|
||||||
|
|
||||||
|
**구조**:
|
||||||
|
```tsx
|
||||||
|
// InteractionHeader.tsx
|
||||||
|
<>
|
||||||
|
{/* Sentinel - 스크롤 감지용 */}
|
||||||
|
<Box ref={sentinelRef} h="1px" />
|
||||||
|
|
||||||
|
{/* Sticky Header */}
|
||||||
|
<Box
|
||||||
|
position="sticky"
|
||||||
|
top="0rem"
|
||||||
|
zIndex={100}
|
||||||
|
bg={isSticky ? "bg/80" : "transparent"}
|
||||||
|
backdropFilter={isSticky ? "blur(12px)" : "none"}
|
||||||
|
borderBottom={isSticky ? "1px solid" : "none"}
|
||||||
|
boxShadow={isSticky ? "sm" : "none"}
|
||||||
|
transition="all 0.2s ease"
|
||||||
|
>
|
||||||
|
<Container maxW="95vw">
|
||||||
|
<HStack justify="space-between">
|
||||||
|
{/* 왼쪽: 뒤로가기 + 제목 */}
|
||||||
|
<HStack gap={3} flex={1} minW="0">
|
||||||
|
<BackButton />
|
||||||
|
<Heading
|
||||||
|
size={isSticky ? "xl" : "2xl"}
|
||||||
|
lineClamp={isSticky ? 1 : undefined}
|
||||||
|
transition="font-size 0.2s ease"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 오른쪽: 배지 + 모드 전환 + 이미지 변경 */}
|
||||||
|
<HStack gap={3}>
|
||||||
|
<ScoreBadge size={isSticky ? "sm" : "md"} />
|
||||||
|
<AreaUnlockBadge size={isSticky ? "sm" : "md"} />
|
||||||
|
<EditorModeSwitch />
|
||||||
|
<Button size={isSticky ? "sm" : "md"}>
|
||||||
|
<LuImagePlus />
|
||||||
|
{!isSticky && t('changeImage')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
```
|
||||||
|
|
||||||
|
**IntersectionObserver 로직**:
|
||||||
|
```typescript
|
||||||
|
const [isSticky, setIsSticky] = useState(false);
|
||||||
|
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sentinel = sentinelRef.current;
|
||||||
|
if (!sentinel) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
setIsSticky(!entry.isIntersecting); // sentinel이 안 보이면 sticky
|
||||||
|
},
|
||||||
|
{ threshold: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(sentinel);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
**glassmorphism 효과**:
|
||||||
|
- 반투명 배경 (`bg/80`)
|
||||||
|
- 블러 효과 (`blur(12px)`)
|
||||||
|
- 부드러운 전환 (0.2s ease)
|
||||||
|
|
||||||
|
#### 7:3 그리드 레이아웃
|
||||||
|
|
||||||
|
**구조**:
|
||||||
|
```tsx
|
||||||
|
<Box
|
||||||
|
display="grid"
|
||||||
|
gridTemplateColumns="7fr 3fr" // 이미지 70%, 컨트롤러 30%
|
||||||
|
gap={6}
|
||||||
|
alignItems="start"
|
||||||
|
>
|
||||||
|
{/* 왼쪽: 이미지 영역 */}
|
||||||
|
<VStack gap={4} alignItems="stretch" minW="0" width="100%">
|
||||||
|
<InteractiveImageViewer mode="editor" ... />
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
{/* 오른쪽: 컨트롤러 영역 (스크롤 가능) */}
|
||||||
|
<Box
|
||||||
|
maxH="80dvh"
|
||||||
|
overflowY="auto"
|
||||||
|
overflowX="hidden"
|
||||||
|
pr={2}
|
||||||
|
css={{
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
width: '8px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
background: 'var(--chakra-colors-border-muted)',
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VStack gap={5} alignItems="stretch">
|
||||||
|
{/* 모드 전환 스위치 */}
|
||||||
|
{/* 에디터 버튼 (추가/삭제/숨기기) */}
|
||||||
|
{/* 영역 목록 (일반/고급 모드 공통) */}
|
||||||
|
{/* 파라미터 패널 (고급 모드 전용) */}
|
||||||
|
{/* 선택된 영역 설정 */}
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
**주요 특징**:
|
||||||
|
- ✅ **한 화면 편집**: 이미지 보면서 인터랙션 수정 (스크롤 불필요)
|
||||||
|
- ✅ **독립 스크롤**: 컨트롤러만 내부 스크롤 (80dvh 제한)
|
||||||
|
- ✅ **반응형 이미지**: `width="100%"` + `aspectRatio` 유지
|
||||||
|
- ✅ **Container 확장**: `maxW="95vw"` (화면 최대 활용)
|
||||||
|
|
||||||
|
#### framer-motion 애니메이션 패턴
|
||||||
|
|
||||||
|
**MotionBox 생성**:
|
||||||
|
```typescript
|
||||||
|
import {motion, AnimatePresence} from "framer-motion";
|
||||||
|
|
||||||
|
const MotionBox = motion.create(Box);
|
||||||
|
const MotionVStack = motion.create(VStack);
|
||||||
|
```
|
||||||
|
|
||||||
|
**AnimatePresence 활용**:
|
||||||
|
```tsx
|
||||||
|
{/* 고급 모드 에디터 컨트롤 패널 */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{showEditor && isAdvanced && (
|
||||||
|
<MotionBox
|
||||||
|
key="editor-controls"
|
||||||
|
initial={{opacity: 0, height: 0}}
|
||||||
|
animate={{opacity: 1, height: "auto"}}
|
||||||
|
exit={{opacity: 0, height: 0}}
|
||||||
|
transition={{duration: 0.3, ease: [0.22, 1, 0.36, 1]}}
|
||||||
|
>
|
||||||
|
{/* 영역 목록 + 파라미터 패널 */}
|
||||||
|
</MotionBox>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* 선택된 영역 설정 */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{selectedArea && (
|
||||||
|
<MotionVStack
|
||||||
|
key="selected-area-panel" // 영역 변경 시 재마운트 방지
|
||||||
|
initial={{opacity: 0, y: 10}}
|
||||||
|
animate={{opacity: 1, y: 0}}
|
||||||
|
exit={{opacity: 0, y: 10}}
|
||||||
|
transition={{duration: 0.3, ease: [0.22, 1, 0.36, 1]}}
|
||||||
|
>
|
||||||
|
{/* 모션/이징/속도/강도 선택기 */}
|
||||||
|
</MotionVStack>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* 조건부 UI (모션 있을 때만 표시) */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{selectedArea.movement.preset !== 'none' && (
|
||||||
|
<MotionBox
|
||||||
|
key="easing-selector"
|
||||||
|
initial={{opacity: 0, height: 0}}
|
||||||
|
animate={{opacity: 1, height: "auto"}}
|
||||||
|
exit={{opacity: 0, height: 0}}
|
||||||
|
transition={{duration: 0.25, ease: [0.22, 1, 0.36, 1]}}
|
||||||
|
>
|
||||||
|
<SimpleEasingSelector ... />
|
||||||
|
</MotionBox>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
```
|
||||||
|
|
||||||
|
**애니메이션 특징**:
|
||||||
|
- ✅ **Easing Curve**: `[0.22, 1, 0.36, 1]` (자연스러운 감속)
|
||||||
|
- ✅ **Duration**: 0.2~0.3초 (빠르면서도 부드러운)
|
||||||
|
- ✅ **mode="wait"**: 퇴장 완료 후 등장 시작
|
||||||
|
- ✅ **key 전략**: 고정 key로 영역 변경 시 재마운트 방지
|
||||||
|
|
||||||
|
#### 반응형 이미지 처리 (aspectRatio 충돌 해결)
|
||||||
|
|
||||||
|
**문제**: VStack `maxH` + Box `aspectRatio` 충돌 → 이미지 찌그러짐
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```tsx
|
||||||
|
{/* VStack에서 maxH 제거 */}
|
||||||
|
<VStack gap={4} alignItems="stretch" minW="0" width="100%">
|
||||||
|
{/* aspectRatio가 자유롭게 작동 */}
|
||||||
|
</VStack>
|
||||||
|
```
|
||||||
|
|
||||||
|
**InteractiveImageViewer 개선**:
|
||||||
|
```tsx
|
||||||
|
// 에디터 모드
|
||||||
|
<Box
|
||||||
|
width="100%" // 부모 크기 따름
|
||||||
|
aspectRatio={imageSize.width / imageSize.height} // 비율 유지
|
||||||
|
>
|
||||||
|
<EditorCanvas ... />
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
**라이브러리 동작**:
|
||||||
|
- EditorCanvas: `width: 100%, height: 100%` (부모 크기 따름)
|
||||||
|
- ImageDistortion: `width: 100%, height: 100%` (부모 크기 따름)
|
||||||
|
- ResizeObserver로 실제 렌더링 크기 측정
|
||||||
|
|
||||||
|
#### z-index 계층 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
Navbar (z-index: 1000)
|
||||||
|
└─ Sticky Headers (z-index: 100)
|
||||||
|
├─ WritingDetailHeader
|
||||||
|
└─ InteractionHeader
|
||||||
|
└─ 하단 저장 버튼 바 (z-index: 10)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 주요 컴포넌트
|
||||||
|
|
||||||
|
| 컴포넌트 | 파일 | 역할 |
|
||||||
|
|---------|------|------|
|
||||||
|
| **InteractionHeader** | `src/components/interaction/InteractionHeader.tsx` | Sticky header (제목, 배지, 모드 전환, 이미지 변경) |
|
||||||
|
| **CustomAreaList** | `src/components/interaction/CustomAreaList.tsx` | 왜곡 영역 목록 (일반/고급 모드 공통) |
|
||||||
|
| **CustomParameterPanel** | `src/components/interaction/CustomParameterPanel.tsx` | 파라미터 패널 (고급 모드 전용) |
|
||||||
|
|
||||||
|
#### 성능 최적화
|
||||||
|
|
||||||
|
- ✅ **IntersectionObserver**: scroll 이벤트 대비 성능 우수
|
||||||
|
- ✅ **ResizeObserver**: Canvas 크기 동적 조정
|
||||||
|
- ✅ **AnimatePresence**: DOM 제거 시에도 애니메이션
|
||||||
|
- ✅ **key 전략**: 불필요한 재마운트 방지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 참고 문서
|
## 참고 문서
|
||||||
|
|
||||||
- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조
|
- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user