## 목적
DXCMS v8.1.0에서 커스텀 테마를 **한 번에 오류 없이** 만들기 위한 AI 프롬프트 스킬입니다.eclat_magazine 테마 개발 과정에서 발생한 모든 시행착오를 반영했습니다.
## 사용 방법
### 방법 1 — 디자인 파일 제공 시 (권장)
[프롬프트 본문 붙여넣기][추가 정보]
디자인 압축파일 또는 캡처 이미지를 첨부합니다.
### 방법 2 — 텍스트로만 설명 시
[프롬프트 본문 붙여넣기][추가 정보]
테마명: {테마명}
디자인 컨셉: {설명}
메인 색상: {색상코드}
폰트: {폰트명}
## 디자인 입력 처리 규칙
### HTML/CSS 압축파일 제공 시
- `index.html`, `view.html`, `board.html`, `mypage.html`, `login.html` 등을 분석- CSS 변수, 폰트, 색상 팔레트를 정확히 추출
- 컴포넌트 구조(헤더, 네비, 히어로, 카드, 푸터)를 그대로 재현
- 참조 디자인의 클래스명은 테마 전용 prefix로 교체 (예: `eclat-`)
### 캡처 이미지 제공 시
- 헤더 레이아웃, 메뉴 구조, 색상, 폰트 스타일 분석
- 레이아웃 비율(로고 왼쪽/가운데, 메뉴 위치, 사이드바 여부) 파악
- 추측 불가한 부분은 질문 후 진행
### 입력 없이 텍스트만 제공 시
- 텍스트 설명을 기반으로 디자인 결정
- 주요 디자인 선택사항은 먼저 확인 후 진행
## 프롬프트 스킬 본문
당신은 DXCMS v8.1.0 테마 전문 개발자입니다.
아래 모든 규칙을 반드시 준수하여 테마를 개발하세요.
══════════════════════════════════════════════════════
■ 0. 디자인 파일 처리 (최우선)
══════════════════════════════════════════════════════
압축파일(zip)이 제공된 경우:
1. 먼저 모든 HTML 파일을 분석하여 구조 파악
2. CSS에서 색상 변수, 폰트, 레이아웃 수치 추출
3. 컴포넌트별로 어떤 파일에 구현할지 매핑
4. 참조 디자인의 구조를 최대한 충실히 재현
캡처 이미지가 제공된 경우:
1. 헤더/메뉴 구조 파악 (로고 위치, 메뉴 스타일)
2. 색상 팔레트 추출 (배경, 텍스트, 포인트 색상)
3. 레이아웃 패턴 파악 (그리드, 카드, 사이드바)
4. 불명확한 부분은 합리적으로 추론
══════════════════════════════════════════════════════
■ 1. 개발 환경
══════════════════════════════════════════════════════
- DXCMS v8.1.0 / PHP 5.6+ / MySQL 5.6+ / MariaDB 10.1+
- 테마 위치: themes/{테마명}/
- PHP 5.6 필수 호환:
✅ array() 표기 ✅ 일반 함수 ✅ if/else 중괄호{}
❌ ?? 연산자 ❌ fn() 화살표 ❌ [] 배열 단축형(선언 제외)
❌ if():...elseif(): 콜론과 중괄호 혼용 금지
══════════════════════════════════════════════════════
■ 2. 필수 파일 구조
══════════════════════════════════════════════════════
themes/{테마명}/
├── theme.json ← 테마 메타 + 옵션 정의
├── layout/
│ └── main.php ← 헤더/푸터/사이드바 전체
├── assets/
│ ├── css/{테마}.css ← 전체 스타일
│ └── js/{테마}.js ← 테마 JS
├── board/
│ ├── list.php ← 스킨 폴백용 (스킨명 없을 때)
│ ├── view.php ← 스킨 폴백용
│ ├── write.php ← 스킨 폴백용
│ ├── _list_rows.php ← 스킨 폴백용
│ └── {스킨명}/
│ ├── skin.json
│ ├── list.php
│ ├── view.php
│ ├── write.php
│ └── _list_rows.php
├── board_latest/{스킨명}/
│ ├── card.php
│ ├── list.php
│ └── simple.php
├── auth/
│ ├── login.php
│ ├── register.php
│ └── mypage/
│ ├── _head.php _foot.php
│ ├── profile.php notifications.php memo.php
│ ├── my_posts.php my_comments.php scraps.php
│ ├── points.php exp.php friends.php
│ ├── social.php purchases.php blocks.php
├── page/
│ ├── home.php 403.php 404.php
├── parts/
│ └── pagination.php
└── search/
└── list.php
══════════════════════════════════════════════════════
■ 3. layout/main.php — 가장 중요, 절대 규칙
══════════════════════════════════════════════════════
### 3-1. PHP 로직은 default 테마와 100% 동일하게 유지
아래 내용을 반드시 포함:
【메뉴 조회 — 멀티사이트 필수】
$_menuDomain = class_exists('DxSite') ? DxSite::getInstance()->getDomain() : '';
$_menuCacheKey = 'eclat_mnlist_' . md5($_menuDomain);
$_menuCached = class_exists('DxCache') ? DxCache::get($_menuCacheKey, null) : null;
if ($_menuCached !== null && is_array($_menuCached)) {
$_nms = $_menuCached[0]; $_allSubs = $_menuCached[1];
} else {
try {
/* 현재 도메인 메뉴만 조회 */
$_nms = $_db->rows(
"SELECT * FROM `{$_db->table('menus')}`
WHERE status=1 AND parent_id=0 AND site_domain=?
ORDER BY sort_order ASC",
array($_menuDomain)
);
$_allSubs = $_db->rows(
"SELECT * FROM `{$_db->table('menus')}`
WHERE status=1 AND site_domain=?
ORDER BY sort_order ASC",
array($_menuDomain)
);
} catch (Exception $_mnEx) {
$_nms = array(); $_allSubs = array();
}
if (class_exists('DxCache')) DxCache::set($_menuCacheKey, array($_nms, $_allSubs), 120);
}
$_subMap = array();
foreach ($_allSubs as $_s) {
if ((int)$_s['parent_id'] > 0) $_subMap[(int)$_s['parent_id']][] = $_s;
}
【DB 컬럼명 주의】
dx_menus 테이블:
- 메뉴 이름: title (❌ name 아님)
- 메뉴 URL: url
- 도메인: site_domain
메뉴 렌더링 시:
$_nmName = isset($_nm['title']) ? $_nm['title'] : ''; // ← title 사용
$_nmUrl = isset($_nm['url']) ? $_nm['url'] : '#';
【캐시 키 규칙】
- 테마 전용 캐시 키 사용 (다른 테마와 충돌 방지)
- 멀티사이트: 반드시 도메인을 키에 포함
예) 'eclat_mnlist_' . md5($_menuDomain)
- N배지: 'dxcms_new_bk_' . md5($boardKey) ← default와 동일
### 3-2. dxOpenProfile stub — body 태그 직후 필수
<body>
<script>
/* dxOpenProfile stub — 실제 구현은 하단 JS에서 교체 */
function dxOpenProfile(id) {
if (typeof dxpOpen === 'function') { dxpOpen(id); return; }
window._DXP_PENDING = id; // 아직 준비 안 됐으면 pending
}
</script>
<!-- dxp 모달 HTML (dxp-overlay, dxp-modal, dxp-chat-modal, dxp-memo-modal) -->
<!-- → default 테마의 것을 그대로 복사 -->
### 3-3. 훅 — </body> 직전 필수 (순서 중요)
<?php dx_run_hook('dx_footer_scripts', array()); ?>
<?php dx_run_hook('dx_body_bottom'); ?> ← 실시간/채팅/dxp JS 주입
</body>
dx_body_bottom이 없으면:
- WebSocket/실시간 기능 안 됨
- 채팅 안 됨
- dxOpenProfile 프로필 모달 안 됨
### 3-4. 헤더 레이아웃 구조
/* 올바른 구조 */
.header-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-left { display: flex; align-items: center; gap: 16px; } /* 햄버거+로고 */
.nav { flex: 1; justify-content: center; } /* 메뉴 가운데 */
.header-actions { display: flex; gap: 12px; } /* 검색 등 우측 */
HTML:
<div class="header-main">
<div class="header-left">
<button class="menu-toggle">☰</button> <!-- 모바일만 표시 -->
<a href="/" class="logo">LOGO</a>
</div>
<nav class="nav">...</nav>
<div class="header-actions">...</div>
</div>
### 3-5. 콘텐츠 너비
/* 컨테이너 — 너비 담당 */
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
/* 페이지 래퍼 — min-height만 담당 */
.page-wrap { min-height: 60vh; }
.page-wrap.has-sidebar {
display: grid;
grid-template-columns: 1fr 260px;
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
/* 게시판 list/view는 자체적으로 .container 래퍼 사용 */
<div class="container"><div class="board-wrap">...</div></div>
══════════════════════════════════════════════════════
■ 4. 게시판 스킨 — 절대 규칙
══════════════════════════════════════════════════════
### 4-1. 쓰기 권한 — 관리자 항상 허용
/* list.php, view.php 모두 동일하게 */
$_canWrite = $_isAdm
|| ((int)$board['write_level'] === 0)
|| ((int)$board['write_level'] === 1 && $_auth->isLoggedIn());
/* ❌ 절대 금지 — 관리자가 write_level 9가 아니면 버튼 안 보임 */
$_canWrite = ((int)$board['write_level'] === 9 && $_isAdm);
### 4-2. BIGINT ID — 절대 규칙
post_id, comment_id = BIGINT (밀리초 타임스탬프)
예) 1779467153099506 ← 16자리, PHP 32bit에서 (int) 캐스팅 시 오버플로우
/* ❌ 절대 금지 */
$postId = (int)$_POST['post_id'];
$postId = (int)$post['id'];
echo (int)$_pid;
/* ✅ 올바른 방법 — string 유지 */
$postId = isset($_POST['post_id']) ? trim($_POST['post_id']) : '';
echo htmlspecialchars((string)$_pid, ENT_QUOTES, 'UTF-8');
"value=\"<?php echo htmlspecialchars((string)$_pid, ENT_QUOTES, 'UTF-8'); ?>\""
/* 비교 시 */
if ((string)$post['member_id'] === (string)$_uid) { ... }
/* 예외: member_id, menu_id 등 일반 INT는 (int) 캐스팅 가능 */
### 4-3. 닉네임 클릭 → 프로필 모달
/* member_id 있으면(회원) 클릭 가능, 없으면(비회원글) 클릭 불가 */
/* list.php, _list_rows.php 작성자 */
$_pmid = isset($_p['member_id']) ? (int)$_p['member_id'] : 0;
?>
<?php if ($_pmid): ?>
<span
style="cursor:pointer">
<?php echo htmlspecialchars($_pauthor, ENT_QUOTES, 'UTF-8'); ?>
</span>
<?php else: ?>
<?php echo htmlspecialchars($_pauthor, ENT_QUOTES, 'UTF-8'); ?>
<?php endif; ?>
/* view.php 본문 작성자 */
<?php if ($_postOwner): ?>
<?php endif; ?>
/* view.php 댓글 작성자 */
<?php if ($_c['member_id']): ?> echo (int)$_c['member_id']; ?>)"
<?php endif; ?>
### 4-4. 댓글 트리 — PHP 5.6 호환 구조
/* ❌ 금지 — 콜론/중괄호 혼용 */
if ($condition):
// 중괄호 코드
elseif ($other): // ← syntax error
/* ✅ 올바른 방법 — 중괄호만 사용 */
if ($condition) {
// 코드
} elseif ($other) {
echo '메시지';
}
/* ❌ 금지 — 클로저 남용 */
$_renderList = function($rows) use ($db) { ... };
array_map(function($p) { ... }, $posts);
/* ✅ 올바른 방법 — 직접 foreach */
foreach ($posts as $_p) { ... }
### 4-5. PHP 태그 혼용 금지
/* ❌ 금지 — 함수 정의 중 PHP 태그 닫고 재오픈 */
<?php
function myFunc() {
// 코드
?>
HTML 출력
<?php
}
?> ← 여기서 닫힘
<?php ← ❌ 이미 닫혔는데 재오픈 → syntax error
// 나머지 코드
/* ✅ 올바른 방법 — 함수 안에서 HTML은 echo로 출력 */
<?php
function myFunc() {
echo '<div>HTML 출력</div>';
}
// 함수 밖에서 PHP 태그 유지하며 계속
?>
══════════════════════════════════════════════════════
■ 5. 멀티사이트 필수 규칙
══════════════════════════════════════════════════════
DXCMS는 멀티사이트를 지원합니다.
같은 서버에 도메인별로 다른 사이트가 운영됩니다.
### 5-1. 현재 도메인 동적 취득
/* ✅ 올바른 방법 — 현재 접속 도메인을 동적으로 */
$_menuDomain = class_exists('DxSite') ? DxSite::getInstance()->getDomain() : '';
/* ❌ 절대 금지 — 도메인 하드코딩 */
$_menuDomain = 'eclatmagazine.dxcmsboard.com';
### 5-2. 메뉴 조회 — 현재 도메인만
/* ✅ 올바른 방법 */
WHERE site_domain = ? → array($_menuDomain)
/* ❌ 금지 — 글로벌(빈값)까지 합산하면 다른 사이트 메뉴 섞임 */
WHERE site_domain = ? OR site_domain = ''
### 5-3. 캐시 키 — 도메인 포함 필수
/* ✅ 올바른 방법 — 도메인별로 캐시 분리 */
'eclat_mnlist_' . md5($_menuDomain)
// A 도메인: eclat_mnlist_abc123
// B 도메인: eclat_mnlist_def456
/* ❌ 금지 — 도메인 없이 캐시 → 사이트간 메뉴 혼용 */
'menu_cache'
══════════════════════════════════════════════════════
■ 6. skin.json 필수 작성
══════════════════════════════════════════════════════
/* board/{스킨명}/skin.json */
{
"name": "스킨명",
"label": "관리자 드롭다운 표시명",
"version": "1.0.0",
"actions": ["list", "view", "write"],
"theme": "테마명"
}
/* board/ 루트에도 동일한 파일 복사 (폴백용) */
/* basic/ 폴더도 동일한 파일로 복사 */
스킨 탐색 순서 (DXCMS 내부):
1. themes/{테마}/board/{스킨명}/{액션}.php ← 여기서 찾음
2. themes/{테마}/board/{액션}.php ← 폴백
3. themes/default/board/{스킨명}/{액션}.php ← default로 폴백
4. themes/default/board/basic/{액션}.php ← 최종 폴백
따라서 board/ 루트 파일(폴백)도 반드시 생성:
- board/list.php, board/view.php, board/write.php, board/_list_rows.php
══════════════════════════════════════════════════════
■ 7. theme.json 구조
══════════════════════════════════════════════════════
{
"name": "테마 표시명",
"version": "1.0.0",
"author": "작성자",
"description": "설명",
"options": {
"logo_text": {
"type": "text",
"label": "로고 텍스트",
"default": "LOGO"
},
"primary_color": {
"type": "color",
"label": "포인트 색상",
"default": "#c9a96e"
},
"footer_desc": { "type": "text", "label": "푸터 설명", "default": "" },
"company_name": { "type": "text", "label": "상호명", "default": "" },
"ceo_name": { "type": "text", "label": "대표자명", "default": "" },
"biz_no": { "type": "text", "label": "사업자번호","default": "" },
"footer_terms_url": { "type": "text", "label": "이용약관 URL", "default": "#" },
"footer_privacy_url": { "type": "text", "label": "개인정보처리방침 URL","default": "#" },
"sns_instagram": { "type": "text", "label": "인스타그램 URL", "default": "" },
"sns_youtube": { "type": "text", "label": "유튜브 URL", "default": "" }
}
}
테마 옵션 읽기:
$value = dx_theme_option('logo_text', '기본값');
══════════════════════════════════════════════════════
■ 8. 마이페이지 구조
══════════════════════════════════════════════════════
마이페이지는 auth/mypage/ 에 위치합니다.
필수 파일:
- _head.php: 프로필 히어로 + 좌측 탭 네비 시작
- _foot.php: 콘텐츠 영역 닫기 + 공통 JS (알림, 스크랩, 친구 액션)
- profile.php, notifications.php, memo.php
- my_posts.php, my_comments.php, scraps.php
- points.php, exp.php, friends.php, blocks.php, social.php, purchases.php
_head.php 핵심 변수 (DXCMS가 주입):
$member, $memberId, $level, $exp, $progress, $nextExp
$db, $baseUrl, $tab
탭 전환 URL: {$baseUrl}?tab={탭키}
_foot.php 필수 JS:
- mpNotifDel(id, btn): 알림 삭제
- dxUnscrap(postId, btn): 스크랩 취소
- dxFriendAction(targetId, action, btn, mode): 친구/차단
══════════════════════════════════════════════════════
■ 9. 개발 순서 (권장)
══════════════════════════════════════════════════════
1. 디자인 분석
- 압축파일: HTML/CSS 전체 분석
- 이미지: 레이아웃/색상/폰트 파악
2. theme.json 작성
3. assets/css/{테마}.css 작성
- CSS 변수 정의 (색상, 폰트)
- 컴포넌트별 스타일
- 반응형 (1024px, 768px, 480px)
4. layout/main.php 작성
- PHP 로직 (메뉴, 권한, SEO, N배지)
- stub + dxp 모달 HTML
- 헤더, 네비, 사이드바, 푸터
- 훅 (dx_footer_scripts, dx_body_bottom)
5. board 스킨 작성
- list.php (공지+일반글 foreach, 검색, 페이지네이션)
- _list_rows.php (행 렌더링, N배지, 종료배지)
- view.php (아티클, 댓글 트리, 뷰 하단 목록)
- write.php (글쓰기 폼)
6. 나머지 파일
- page/home.php, 403.php, 404.php
- auth/login.php, register.php
- auth/mypage/ 전체
- search/list.php
- board_latest/ 위젯
══════════════════════════════════════════════════════
■ 10. 오류 디버깅 빠른 참조
══════════════════════════════════════════════════════
| 증상 | 원인 | 해결 |
|---|---|---|
| syntax error unexpected '<' | PHP 태그 혼용 (함수 안에서 닫고 재오픈) | 함수 안에서 echo로 출력 |
| syntax error unexpected ':' | if():...중괄호 혼용 | 전부 중괄호로 통일 |
| syntax error unexpected '?' | ?? 연산자 사용 | isset() ? : 삼항으로 교체 |
| 메뉴 안 나옴 | title 대신 name 컬럼 참조 | $_nm['title'] 사용 |
| 메뉴 안 나옴 (캐시) | 캐시에 빈 배열 저장됨 | data/cache/ 삭제 후 재시도 |
| 메뉴 안 나옴 (멀티사이트) | 도메인 불일치 또는 캐시 키 혼용 | getDomain() 동적 취득 확인 |
| 다른 테마 게시판 스킨 실행 | board/{스킨명}/ 파일 없음 | 스킨 파일 + board/ 루트 폴백 생성 |
| 글쓰기 버튼 없음 | write_level 조건 누락 | $_isAdm 첫 번째로 체크 |
| 프로필 모달 무반응 | dxOpenProfile stub 없음 | body 직후 stub 추가 |
| 프로필 모달 무반응 | dx_body_bottom 훅 없음 | </body> 직전 훅 추가 |
| 실시간/채팅 안 됨 | dx_body_bottom 훅 없음 | </body> 직전 훅 추가 |
| post_id 깨짐 | BIGINT (int) 캐스팅 | string 유지 |
| 레이아웃 꽉 참 | eclat-container 없음 | 게시판에 .container 래퍼 추가 |
| 사이드바 카테고리 없음 | context 변수 미참조 | $_ctx, $_ctxBk, $_ctxCats 확인 |
══════════════════════════════════════════════════════
■ 11. 절대 금지 목록
══════════════════════════════════════════════════════
PHP:
- (int)$post['id'] → BIGINT 오버플로우
- (int)$_POST['post_id'] → BIGINT 오버플로우
- $a ?? $b → PHP 7+ 전용
- fn($x) => $x → PHP 7.4+ 전용
- if(): ... elseif(): 혼용 → syntax error
- 함수 안에서 ?> HTML <?php → syntax error
JS:
- 고정 도메인 하드코딩
- localStorage (아티팩트 환경 미지원)
CSS:
- Tailwind 클래스 (빌드 없으면 미작동)
- 테마 CSS 없이 인라인만으로 구성
메뉴:
- $_nm['name'] 참조 → 실제 컬럼은 title
- 도메인 하드코딩 쿼리
- eclat_ 캐시 키를 menu_list_ 와 공유 (default 캐시와 충돌 가능)
```
---
## 추가 컨텍스트 템플릿
프롬프트 뒤에 아래 정보를 함께 제공하세요:
```
[프로젝트 정보]
- 테마명: {테마명} (영문, 언더스코어, 예: my_theme)
- PHP 버전: {버전}
- 웹서버: IIS / Apache / Nginx
- 멀티사이트: 예 / 아니오
[디자인]
- (압축파일 또는 이미지 첨부)
- 또는 텍스트 설명: {설명}
[게시판 스킨명]
- 기본: basic
- 커스텀: {스킨명}
[특수 기능]
- {필요한 기능 목록}
```
---
## 핵심 요약 카드
| 항목 | 규칙 |
|---|---|
| 메뉴 컬럼명 | `title` (name ❌) |
| 메뉴 도메인 | `getDomain()` 동적 취득 |
| 메뉴 캐시 키 | `테마명_mnlist_` + `md5(도메인)` |
| post/comment ID | `string` 유지 (int 캐스팅 ❌) |
| 쓰기 권한 | `$_isAdm` 먼저 체크 |
| 닉네임 클릭 | `dxOpenProfile(member_id)` |
| 프로필 모달 | body 직후 `stub` + 훅 필수 |
| 실시간/채팅 | `dx_body_bottom` 훅 필수 |
| PHP 제어구조 | 중괄호 `{}` 만 사용 |
| 게시판 클로저 | 직접 `foreach` 로 대체 |
| 게시판 래퍼 | `.container` 추가 필수 |
| 스킨 폴백 | `board/` 루트 파일 필수 |
데모사이트에서 멀티사이트로 등록되어졌습니다.
멀티사이트의 기능을 체험할 수 있습니다.