feat: 로드나인 프로모션 - 제플린 가이드

This commit is contained in:
saedo
2026-03-24 15:27:09 +09:00
parent cec4bb6771
commit 2f4532815e

View File

@@ -0,0 +1,353 @@
# 프로모션 - 기본 템플릿 Zeplin MCP 마크업 작업 가이드
이 문서는 Zeplin MCP를 활용한 프로모션 페이지 마크업 작업 방식을 정리한 가이드입니다.
---
## 1. 환경 설정 (최초 1회)
### Zeplin MCP SSL 인증서 오류 해결
사내 네트워크/VPN 환경에서 Zeplin MCP가 `unable to get local issuer certificate` 오류를 발생시키는 경우, `~/.cursor/mcp.json`의 Zeplin 서버 `env`에 아래 항목을 추가해야 합니다.
```json
{
"mcpServers": {
"zeplin": {
"timeout": 600,
"command": "npx",
"args": ["-y", "@zeplin/mcp-server@latest"],
"env": {
"ZEPLIN_ACCESS_TOKEN": "...",
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
```
설정 변경 후 **Cursor 재시작** 필요 (`Cmd+Shift+P``Developer: Reload Window`).
> `NODE_TLS_REJECT_UNAUTHORIZED=0`은 로컬 Cursor MCP 전용 설정이며 소스코드/배포 환경과 완전히 분리됩니다.
---
## 2. 프로젝트 구조
```
l9-fe/
├── pages/promotion/{프로젝트명}/{날짜}/index.vue
├── assets/scss/pages/promotion/{프로젝트명}/{날짜}/index.scss
└── public/assets/promotion/{프로젝트명}/{날짜}/
├── pc/
│ ├── bg_section01_ko.jpg
│ ├── bg_section01_tw.jpg
│ └── ...
└── mobile/
├── bg_section01.jpg ← 기본 BG (언어 무관, 배경 전용 화면에서 다운로드)
├── bg_section01_ko.jpg ← KO 콘텐츠 이미지 (KO 콘텐츠 화면에서 다운로드)
├── bg_section01_tw.jpg ← TW 콘텐츠 이미지 (TW 콘텐츠 화면에서 다운로드)
└── ...
```
### Vue 파일 주요 구조
- `promotion-content` 클래스: 프로젝트별 고유 클래스명 사용 (예: `dinapheri-update`)
- 섹션 구성: `section01` ~ `sectionN`
- 번역 키: `tm()` 함수로 호출, JSON 파일에서 관리
- SCSS import: `<style>` 블록에서 `@import '@/assets/scss/pages/promotion/.../index.scss'`
---
## 3. Zeplin 화면 종류와 역할
프로모션 페이지는 용도별로 여러 Zeplin 화면이 존재합니다. 작업 전에 각 화면의 역할을 먼저 파악하세요.
### PC 화면
| 화면 역할 | 내용 |
|----------|------|
| PC KR 배경+콘텐츠 | PC용 전체 화면. 섹션 배경 이미지 + 텍스트 레이어 위치 좌표 확인용 |
| PC TW 배경+콘텐츠 | TW 언어용 PC 전체 화면 |
### 모바일 화면 (3종)
모바일은 PC와 달리 **역할이 분리된 화면 3종**을 사용합니다.
| 화면 역할 | 파일명 규칙 | 설명 |
|----------|-----------|------|
| 모바일 BG 전용 (고해상도) | `bg_sectionXX.jpg` | 배경 이미지만 있는 화면. 보통 2048px 너비. `.view-mobile section``background-image`에 사용 |
| 모바일 KO 콘텐츠 | `bg_sectionXX_ko.jpg` | 배경 + KO 콘텐츠가 합성된 화면. `[lang='ko-KR'] section::before``background-image`에 사용 |
| 모바일 TW 콘텐츠 | `bg_sectionXX_tw.jpg` | 배경 + TW 콘텐츠가 합성된 화면. `[lang='zh-TW'] section::before``background-image`에 사용 |
> **섹션 1개당 이미지 3개** 사용: `bg_sectionXX.jpg` + `bg_sectionXX_ko.jpg` + `bg_sectionXX_tw.jpg`
---
## 4. Zeplin MCP 사용법
### 화면 레이어 조회
```
get_screen(url, includeVariants: false)
```
조회 결과에서 확인할 항목:
- 각 섹션 그룹 레이어의 `sourceId`, `rect.y`, `rect.height`
- 텍스트 레이어의 절대 `y` 좌표 (notice, even-date 위치 계산용)
- `exportable: true` 여부 → false이면 다운로드 불가 (디자이너에게 export 설정 요청)
### 레이어 구조 파악 방법
`get_screen`으로 조회하면 최상위 레이어 목록과 각 레이어의 `y`, `height` 값을 확인할 수 있습니다.
섹션별 절대 y 시작값과 높이는 **프로젝트마다 다르므로**, 작업 전 반드시 전체 화면을 조회해서 직접 확인하세요.
```
예시)
레이어명 섹션 절대 y 시작 높이
00_main → section01 0 1020
01_con → section02 1020 2449
02_con → section03 3469 1839
...
```
### 요소 위치 계산 공식
**PC** - CSS `top` (px 고정값):
```
CSS top = 텍스트 레이어 절대 y 해당 섹션 절대 y 시작값
예시) section03의 notice 텍스트
절대 y: 2830 section03 시작 y: 1923 = top: 907px
```
**모바일** - `toPercentage(섹션높이, 상대y)`:
```
상대 y = 텍스트 레이어 절대 y 해당 섹션 절대 y 시작값
CSS top = toPercentage(섹션높이, 상대y)
예시) section03의 even-date (섹션 y: 3469, 섹션 높이: 1839)
텍스트 절대 y: 3697
상대 y: 3697 - 3469 = 228
→ toPercentage(1839, 228)
```
> 모바일 텍스트 좌표는 **콘텐츠 화면(KO/TW)**에서 읽어야 정확합니다. BG 전용 화면에는 텍스트 레이어가 없습니다.
### 이미지 에셋 다운로드
```
download_layer_asset(layerSourceId, localPath, assetType)
```
- 다운로드 시 **UUID 파일명**으로 저장되므로 반드시 rename 필요
- `exportable: false`인 레이어는 다운로드 불가 → 디자이너에게 Zeplin export 설정 요청 후 재시도
- Zeplin 디자인이 수정되면 `sourceId`가 바뀔 수 있으므로 교체 시 화면 재조회 필수
---
## 5. SCSS 작성 규칙
### 전체 구조
```scss
/* ─── PC ─────────────────────────────── */
.view-desktop {
.promotion-content.{프로젝트클래스} {
section { /* 공통 PC 스타일 */ }
.notice-list { /* 공통 */ }
.section01 { height: 826px; }
.section02 {
height: 1100px;
}
/* ... */
}
}
/* ─── 모바일 ──────────────────────────── */
.view-mobile {
.promotion-content.{프로젝트클래스} {
section {
position: relative;
background-position: 50% 0;
background-size: auto 100%;
background-repeat: no-repeat;
&::before {
content: '';
display: block;
max-width: toRem(750);
width: 100%;
margin: 0 auto;
background: 50% 0 / 100% auto no-repeat;
}
.notice-list { /* 공통 모바일 notice-list 스타일 */ }
.even-date { /* 공통 모바일 even-date 스타일 */ }
}
/* 섹션별 개별 설정 */
.section01 {
background-image: url('/assets/.../mobile/bg_section01.jpg'); /* 기본 BG */
&::before { @include inputMobileHeight(1020); } /* 섹션 높이값 */
}
.section02 {
background-image: url('/assets/.../mobile/bg_section02.jpg');
&::before { @include inputMobileHeight(2449); }
.notice-list { top: toPercentage(2449, 2319); }
}
.section03 {
background-image: url('/assets/.../mobile/bg_section03.jpg');
&::before { @include inputMobileHeight(1839); }
.even-date { top: toPercentage(1839, 228); }
.notice-list { top: toPercentage(1839, 1673); }
}
/* ... */
}
}
/* ─── 언어별 이미지 ────────────────────── */
[lang='ko-KR'] {
.view-desktop .promotion-content.{프로젝트클래스} {
.section01 { background-image: url('/assets/.../pc/bg_section01_ko.jpg'); }
/* ... */
}
.view-mobile .promotion-content.{프로젝트클래스} {
/* ::before 에 KO 콘텐츠 이미지 적용 */
.section01::before { background-image: url('/assets/.../mobile/bg_section01_ko.jpg'); }
/* ... */
}
}
[lang='zh-TW'] {
.view-desktop .promotion-content.{프로젝트클래스} {
.section01 { background-image: url('/assets/.../pc/bg_section01_tw.jpg'); }
/* ... */
}
.view-mobile .promotion-content.{프로젝트클래스} {
.section01::before { background-image: url('/assets/.../mobile/bg_section01_tw.jpg'); }
/* ... */
}
}
```
### 모바일 이미지 레이어링 원리 (모바일 전용)
> 아래 구조는 **모바일에만 해당**합니다. PC는 section에 배경 이미지 하나만 사용합니다.
```
[section 요소 자체]
background-image: bg_sectionXX.jpg
→ 화면 전체를 채우는 배경 깔판 (언어 무관)
[section::before 요소] ← section 위에 겹쳐지는 콘텐츠 레이어
background-image: bg_sectionXX_ko.jpg ([lang='ko-KR'] 블록)
or bg_sectionXX_tw.jpg ([lang='zh-TW'] 블록)
→ max-width: 750px, margin: 0 auto 로 중앙 정렬
→ padding-top % 로 이미지 비율 유지 (inputMobileHeight mixin)
→ 배경 위에 KO/TW 콘텐츠 이미지가 가운데 자리잡음
```
즉, 배경(bg_sectionXX.jpg)은 section에, 언어별 콘텐츠 이미지(bg_sectionXX_ko/tw.jpg)는 ::before에 분리해서 적용합니다.
**PC는 section에 언어별 배경 이미지 하나만 사용** (`[lang] section { background-image: bg_sectionXX_ko.jpg }`).
### inputMobileHeight 값
`inputMobileHeight(N)``N`은 **Zeplin BG 전용 화면의 섹션 높이(px)**에서 읽어옵니다.
콘텐츠 화면과 BG 화면의 섹션 높이는 동일하므로 어느 쪽에서 읽어도 무방합니다.
### 공통 요소 스타일
| 요소 | PC 주요 스타일 | 모바일 주요 스타일 |
|------|-------------|----------------|
| `.notice-list ul li` | `font-size: 16px`, `line-height: 26px`, `color: #978c7a` | `font-size: toRem(10)`, `padding-left: toRem(12)` |
| `.notice-list li::before` | `content: '•'`, `position: absolute`, `left: 0`, `top: 50%`, `transform: translateY(-50%)` | 동일 |
| `.notice-link` | `font-weight: 600`, `color: #b08442`, `text-decoration: underline` | 동일 |
| `.even-date` | `font-size: 20px`, `color: rgba(156,148,132,0.97)` | `font-size: rem(12)`, `letter-spacing: -0.8px` |
---
## 6. 마크업 패턴
### 기본 섹션 구조 (section01 참고)
```vue
<section id="section01" class="section01">
<h3 class="blind">
<span>{{ tm('ProjectName_Section01_SubTitle') }}</span>
<em>{{ tm('ProjectName_Section01_Title') }}</em>
</h3>
<p class="blind">{{ tm('ProjectName_Section01_Desc') }}</p>
<button type="button" class="btn-partner"
@click="[sendLog('event-name', 'UI-1'), goToLink(tm('ProjectName_Section01_Btn_Url'), '_blank')]">
<span class="blind">{{ tm('ProjectName_Section01_Btn') }}</span>
</button>
</section>
```
### 이벤트 섹션 구조 (notice-list + even-date 패턴)
```vue
<section id="section03" class="section03">
<h4 class="blind">{{ tm('ProjectName_Section03_Title') }}</h4>
<p class="blind">{{ tm('ProjectName_Section03_Event_Rewards') }}</p>
<p class="even-date">{{ tm('ProjectName_Section03_Date') }}</p>
<div class="notice-list">
<ul>
<template v-for="(notice, noticeIdx) in tm('ProjectName_Section03_Notice_List')" :key="noticeIdx">
<!-- 마지막 항목에만 링크 포함 -->
<li v-if="noticeIdx === tm('ProjectName_Section03_Notice_List').length - 1">
<span v-dompurify-html="tm(`${notice}`)"></span>
<span v-dompurify-html="tm(`${notice}_Link`)"
class="notice-link"
@click="sendLog('event-name', 'UI-XX')">
</span>
</li>
<li v-else v-dompurify-html="tm(`${notice}`)"></li>
</template>
</ul>
</div>
</section>
```
---
## 7. 작업 순서 (새 세션 시작 시)
### PC 작업
1. 이 문서 숙지
2. PC KR 화면 `get_screen` 조회 → 섹션별 레이어 y, height, sourceId 확인
3. 각 섹션 텍스트 레이어 절대 y → 상대 y 계산 (`top: px`)
4. PC KR 이미지 다운로드 → `bg_sectionXX_ko.jpg`로 rename
5. PC TW 화면 조회 → TW 이미지 다운로드 → `bg_sectionXX_tw.jpg`로 rename
6. Vue 마크업 작성
7. SCSS PC 스타일 작성 → 언어별 이미지 경로 블록 추가
### 모바일 추가 작업
8. **모바일 BG 전용 화면** 조회 → 섹션 높이(`inputMobileHeight` 값) 확인
- 이미지 다운로드 → `bg_sectionXX.jpg`로 rename
9. **모바일 KO 콘텐츠 화면** 조회 → 텍스트 레이어 절대 y 확인
- `상대 y = 텍스트 절대 y 섹션 시작 y` 계산
- `toPercentage(섹션높이, 상대y)` 값 SCSS에 적용
- 이미지 다운로드 → `bg_sectionXX_ko.jpg`로 rename
10. **모바일 TW 콘텐츠 화면** 조회 (필요 시)
- 이미지 다운로드 → `bg_sectionXX_tw.jpg`로 rename
11. SCSS 모바일 스타일 작성:
- `.view-mobile section` 공통 스타일
- 섹션별 `background-image`, `inputMobileHeight`, `toPercentage`
- `[lang='ko-KR']` 블록: `section::before { background-image: ..._ko.jpg }`
- `[lang='zh-TW']` 블록: `section::before { background-image: ..._tw.jpg }`
---
## 8. 주의사항
- **`exportable: false` 레이어는 다운로드 불가** → 디자이너에게 Zeplin에서 export 설정 요청 후 재시도
- **Zeplin 디자인 수정 시 sourceId 변경 가능** → 이미지 교체 시 반드시 화면 재조회
- **모바일 텍스트 좌표는 콘텐츠 화면에서 읽을 것** → BG 전용 화면에는 텍스트 레이어 없음
- **`inputMobileHeight` 값 = BG 화면의 섹션 height** → 콘텐츠 화면과 동일하므로 어느 쪽이든 무방
- **번역 키**는 별도 JSON 파일에서 관리 (`tm()` 함수로 호출)
- **`blind` 클래스**는 스크린리더 전용 숨김 텍스트
- SCSS mixin: `inputMobileHeight`, `inputMobileSize`, `toPercentage`, `toRem`, `rem` 등은 프로젝트 공통 정의 사용