# POP 기술 스펙 **버전: v5 (CSS Grid 기반)** --- ## v5 핵심 규칙 ### 1. 그리드 시스템 | 모드 | 화면 너비 | 칸 수 | 대상 기기 | |------|----------|-------|----------| | mobile_portrait | ~479px | 4칸 | 아이폰 SE ~ 갤럭시 S (세로) | | mobile_landscape | 480~767px | 6칸 | 스마트폰 가로, 작은 태블릿 | | tablet_portrait | 768~1023px | 8칸 | iPad Mini ~ iPad Pro (세로) | | tablet_landscape | 1024px~ | 12칸 | 10~14인치 태블릿 가로 (기본) | > **브레이크포인트 기준**: 실제 기기 CSS 뷰포트 너비 기반 (2026-02-06 재설계) ### 2. 위치 지정 ```typescript interface PopGridPosition { col: number; // 시작 열 (1부터) row: number; // 시작 행 (1부터) colSpan: number; // 열 크기 (1~12) rowSpan: number; // 행 크기 (1~) } ``` ### 3. 브레이크포인트 설정 ```typescript const GRID_BREAKPOINTS = { mobile_portrait: { columns: 4, rowHeight: 48, gap: 8, padding: 12, maxWidth: 479, // 아이폰 SE (375px) ~ 갤럭시 S (360px) }, mobile_landscape: { columns: 6, rowHeight: 44, gap: 8, padding: 16, minWidth: 480, maxWidth: 767, // 스마트폰 가로 }, tablet_portrait: { columns: 8, rowHeight: 52, gap: 12, padding: 20, minWidth: 768, // iPad Mini 세로 (768px) maxWidth: 1023, }, tablet_landscape: { columns: 12, rowHeight: 56, gap: 12, padding: 24, minWidth: 1024, // iPad Pro 11 가로 (1194px), 12.9 가로 (1366px) }, }; ``` ### 4. 세로 자동 확장 ```typescript // 캔버스 높이 동적 계산 const MIN_CANVAS_HEIGHT = 600; // 최소 높이 (px) const CANVAS_EXTRA_ROWS = 3; // 항상 유지되는 여유 행 수 const dynamicCanvasHeight = useMemo(() => { // 가장 아래 컴포넌트 위치 계산 const maxRowEnd = visibleComps.reduce((max, comp) => { const rowEnd = pos.row + pos.rowSpan; return Math.max(max, rowEnd); }, 1); // 여유 행 추가하여 높이 계산 const totalRows = maxRowEnd + CANVAS_EXTRA_ROWS; return Math.max(MIN_CANVAS_HEIGHT, totalRows * rowHeight + padding); }, [layout.components, ...]); ``` **특징**: - 디자이너: 세로 무한 확장 (컴포넌트 추가에 제한 없음) - 뷰어: 터치 스크롤로 아래 컴포넌트 접근 가능 --- ## 데이터 구조 ### v5 레이아웃 ```typescript interface PopLayoutDataV5 { version: "pop-5.0"; metadata: { screenId: number; createdAt: string; updatedAt: string; }; gridConfig: { defaultMode: GridMode; maxRows: number; }; components: PopComponentDefinitionV5[]; globalSettings: { backgroundColor: string; padding: number; }; } ``` ### v5 컴포넌트 ```typescript interface PopComponentDefinitionV5 { id: string; type: PopComponentType; // "pop-label" | "pop-button" | ... label: string; gridPosition: PopGridPosition; config: PopComponentConfig; visibility: Record; // 모드별 표시/숨김 modeOverrides?: Record; // 모드별 오버라이드 } ``` ### 컴포넌트 타입 ```typescript type PopComponentType = | "pop-label" // 텍스트 라벨 | "pop-button" // 버튼 | "pop-input" // 입력 필드 | "pop-select" // 선택 박스 | "pop-grid" // 데이터 그리드 | "pop-container"; // 컨테이너 ``` --- ## 크기 프리셋 ### 터치 요소 | 요소 | 일반 | 산업용 | |------|-----|-------| | 버튼 높이 | 48px | 60px | | 입력창 높이 | 48px | 56px | | 터치 영역 | 48px | 60px | ### 폰트 (clamp) | 용도 | 범위 | CSS | |------|-----|-----| | 본문 | 14-18px | `clamp(14px, 1.5vw, 18px)` | | 제목 | 18-28px | `clamp(18px, 2.5vw, 28px)` | ### 간격 | 이름 | 값 | 용도 | |------|---|-----| | sm | 8px | 요소 내부 | | md | 16px | 컴포넌트 간 | | lg | 24px | 섹션 간 | --- ## 반응형 원칙 ``` 누르는 것 → 고정 (48px) - 버튼, 터치 영역 읽는 것 → 범위 (clamp) - 텍스트 담는 것 → 칸 (colSpan) - 컨테이너 ``` --- ## 위치 변환 12칸 기준으로 설계 → 다른 모드에서 자동 변환 ```typescript // 12칸 → 4칸 변환 예시 const ratio = 4 / 12; // = 0.333 original: { col: 1, colSpan: 6 } // 12칸에서 절반 converted: { col: 1, colSpan: 2 } // 4칸에서 절반 ``` --- ## Troubleshooting ### 컴포넌트가 얇게 보임 - **증상**: rowSpan이 적용 안됨 - **원인**: gridTemplateRows 고정 px - **해결**: `1fr` 사용 ### 모드 전환 안 됨 - **증상**: 화면 크기 변경해도 레이아웃 유지 - **해결**: `detectGridMode()` 사용 ### 겹침 발생 - **증상**: 컴포넌트끼리 겹침 - **해결**: `resolveOverlaps()` 호출 --- ## 타입 가드 ```typescript // v5 레이아웃 판별 function isV5Layout(data: any): data is PopLayoutDataV5 { return data?.version === "pop-5.0"; } // 사용 예시 if (isV5Layout(savedData)) { setLayout(savedData); } else { setLayout(createEmptyPopLayoutV5()); } ``` --- *상세 아키텍처: [ARCHITECTURE.md](./ARCHITECTURE.md)* *파일 목록: [FILES.md](./FILES.md)*