open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new Exception("ZIP 생성 실패: {$zipPath}");
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
$filePath = $file->getPathname();
$localPath = substr($filePath, strlen($sourceDir) + 1);
if ($file->isDir()) {
$zip->addEmptyDir($localPath);
} else {
$zip->addFile($filePath, $localPath);
}
}
$zip->close();
}
try {
$root = __DIR__;
$themeRoot = $root . '/theme/journal';
// ---------------------------
// 1) 테마 기본 파일
// ---------------------------
put_file($themeRoot.'/theme.config.php', <<<'PHP'
', 0);
add_stylesheet('', 1);
add_javascript('', 0);
include_once(G5_THEME_PATH.'/parts/header/header.php');
PHP
);
put_file($themeRoot.'/tail.php', <<<'PHP'
PHP
);
put_file($themeRoot.'/parts/main/main.php', <<<'PHP'
HEADLINE
메인 헤드라인 영역(연동 전)
이 영역은 ‘가장 최신/중요 기사 1건’을 연결하세요.
YYYY-MM-DD
Category
전체 기사 보기
VOL. 384 | Monthly Laser Technology
과월호 보기
Editor’s Pick
이번 호 추천 기사
요약문 자리(2~3줄)
YYYY-MM-DDCategory
'focus', 'title'=>'포커스', 'sub'=>'Industry Focus'),
array('id'=>'interview', 'title'=>'인터뷰', 'sub'=>'Interviews'),
array('id'=>'market', 'title'=>'레이저시장', 'sub'=>'Laser Market'),
array('id'=>'trend', 'title'=>'관련산업동향', 'sub'=>'Industry Trend'),
array('id'=>'photo', 'title'=>'포토이슈', 'sub'=>'Photo Issue'),
array('id'=>'tech', 'title'=>'신기술', 'sub'=>'New Technology'),
array('id'=>'product', 'title'=>'신제품', 'sub'=>'New Products'),
);
?>
기사
요약문 자리(2~3줄)
YYYY-MM-DD
PHP
);
put_file($themeRoot.'/parts/footer/footer.php', <<<'PHP'
PHP
);
// ---------------------------
// 3) assets: css/js/img(svg)
// ---------------------------
put_file($themeRoot.'/assets/css/journal.base.css', <<<'CSS'
.jrnl { color:#111; font-family: system-ui, -apple-system, Segoe UI, Roboto, "Noto Sans KR", sans-serif; }
.jrnl * { box-sizing: border-box; }
.jrnl a { color: inherit; text-decoration: none; }
.jrnl img { max-width:100%; display:block; }
.jrnl__container { width: min(1200px, calc(100% - 32px)); margin:0 auto; }
.jrnl__btn { display:inline-block; padding:10px 14px; border:1px solid #111; background:#111; color:#fff; border-radius:10px; }
CSS
);
put_file($themeRoot.'/assets/css/journal.layout.css', <<<'CSS'
#jrnl-header .jrnl__topbar { background:#f6f6f6; font-size:14px; }
#jrnl-header .jrnl__utility { display:flex; gap:12px; justify-content:flex-end; padding:8px 0; }
#jrnl-header .jrnl__gnb { border-bottom:1px solid #eee; background:#fff; }
#jrnl-header .jrnl__gnbRow { display:flex; align-items:center; gap:16px; padding:14px 0; }
#jrnl-header .jrnl__nav { display:flex; gap:14px; margin-left:auto; }
#jrnl-header .jrnl__menuBtn { display:none; margin-left:auto; }
#jrnl-main .jrnl__hero { padding:22px 0; background:#0b0f17; border-bottom:1px solid #111; }
#jrnl-main .jrnl__heroGrid { display:grid; grid-template-columns: 1.6fr 1fr; gap:18px; }
#jrnl-main .jrnl__heroCard { background:#101827; border:1px solid rgba(255,255,255,.08); border-radius:12px; overflow:hidden; color:#fff; }
#jrnl-main .jrnl__heroThumb { background:#05070c; }
#jrnl-main .jrnl__heroBody { padding:16px; }
#jrnl-main .jrnl__kicker { margin:0 0 6px; font-size:12px; letter-spacing:.08em; color:rgba(255,255,255,.7); }
#jrnl-main .jrnl__heroTitle { margin:0 0 8px; font-size:28px; line-height:1.2; }
#jrnl-main .jrnl__heroLead { margin:0 0 10px; color:rgba(255,255,255,.85); }
#jrnl-main .jrnl__meta { display:flex; gap:10px; font-size:12px; color:rgba(255,255,255,.7); }
#jrnl-main .jrnl__btn { background:#fff; color:#111; border-color:#fff; }
#jrnl-main .jrnl__latest { background:#0f172a; border:1px solid rgba(255,255,255,.08); border-radius:12px; padding:16px; color:#fff; }
#jrnl-main .jrnl__latestList { list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:10px; }
#jrnl-main .jrnl__latestItem a { display:flex; justify-content:space-between; gap:12px; color:#fff; }
#jrnl-main .jrnl__latestTitle { color:rgba(255,255,255,.9); }
#jrnl-main .jrnl__latestDate { color:rgba(255,255,255,.6); font-size:12px; }
#jrnl-main .jrnl__issue { padding:28px 0; background:#fff; }
#jrnl-main .jrnl__sections { padding:10px 0 40px; background:#fff; }
#jrnl-main .jrnl__block { padding:22px 0; border-top:1px solid #eee; }
#jrnl-main .jrnl__sectionHead { display:flex; align-items:flex-end; justify-content:space-between; gap:12px; margin-bottom:12px; }
#jrnl-main .jrnl__sectionSub { margin:0; color:#666; font-size:13px; }
#jrnl-main .jrnl__cardGrid { display:grid; grid-template-columns: repeat(3, 1fr); gap:16px; }
#jrnl-main .jrnl__card { border:1px solid #eee; border-radius:12px; overflow:hidden; background:#fff; }
#jrnl-main .jrnl__thumb { background:#f2f2f2; }
#jrnl-main .jrnl__body { padding:14px; }
#jrnl-main .jrnl__tag { margin:0 0 6px; font-size:12px; color:#666; }
#jrnl-main .jrnl__title { margin:0 0 8px; font-size:18px; line-height:1.3; }
#jrnl-main .jrnl__excerpt { margin:0 0 10px; color:#444; font-size:14px; line-height:1.5; }
#jrnl-main .jrnl__meta { display:flex; gap:10px; font-size:12px; color:#777; }
#jrnl-footer { border-top:1px solid #eee; background:#fff; padding:26px 0; }
#jrnl-footer .jrnl__cta { border:1px solid #eee; border-radius:12px; padding:16px; background:#fafafa; margin-bottom:14px; }
#jrnl-footer .jrnl__footerMeta { display:flex; flex-wrap:wrap; gap:12px; align-items:center; justify-content:space-between; }
#jrnl-footer .jrnl__copy { margin:0; color:#666; font-size:13px; }
@media (max-width: 1024px) {
#jrnl-main .jrnl__heroGrid { grid-template-columns: 1fr; }
#jrnl-main .jrnl__cardGrid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
#jrnl-header .jrnl__nav { display:none; }
#jrnl-header .jrnl__menuBtn { display:inline-block; }
#jrnl-main .jrnl__cardGrid { grid-template-columns: 1fr; }
}
CSS
);
put_file($themeRoot.'/assets/js/journal.base.js', <<<'JS'
(function () {
const btn = document.querySelector('[data-jrnl="menu-toggle"]');
const nav = document.querySelector('[data-jrnl="nav"]');
if (!btn || !nav) return;
btn.addEventListener('click', () => {
const isOpen = nav.style.display === 'flex';
nav.style.display = isOpen ? 'none' : 'flex';
nav.style.flexDirection = 'column';
nav.style.gap = '10px';
});
})();
JS
);
// 로고 SVG
put_file($themeRoot.'/assets/img/logo.svg', <<<'SVG'
SVG
);
// 메인 히어로 이미지 (SVG)
put_file($themeRoot.'/assets/img/hero-laser.svg', <<<'SVG'
SVG
);
// 카드용 플레이스홀더 이미지 (SVG)
put_file($themeRoot.'/assets/img/card-placeholder.svg', <<<'SVG'
SVG
);
// ---------------------------
// 4) 게시판 공용 스킨
// ---------------------------
$skinRoot = $themeRoot.'/skin/board/journal_board';
put_file($skinRoot.'/style.css', <<<'CSS'
.jrnl-board .jrnl-board__head { display:flex; justify-content:space-between; align-items:flex-end; gap:12px; margin:18px 0; }
.jrnl-board .jrnl-board__title { margin:0; font-size:26px; }
.jrnl-board .jrnl-board__list { display:grid; grid-template-columns:repeat(3, 1fr); gap:16px; }
.jrnl-board .jrnl-board__item { border:1px solid #eee; border-radius:12px; overflow:hidden; background:#fff; }
.jrnl-board .jrnl-board__thumb { aspect-ratio:16/9; background:#ddd; overflow:hidden; }
.jrnl-board .jrnl-board__thumb img { width:100%; height:100%; object-fit:cover; }
.jrnl-board .jrnl-board__body { padding:14px; }
.jrnl-board .jrnl-board__meta { font-size:12px; color:#777; display:flex; gap:10px; margin-bottom:8px; }
.jrnl-board .jrnl-board__subject { margin:0 0 8px; font-size:18px; line-height:1.3; }
.jrnl-board .jrnl-board__excerpt { margin:0; color:#444; font-size:14px; line-height:1.5; }
@media (max-width:1024px){
.jrnl-board .jrnl-board__list { grid-template-columns:repeat(2, 1fr); }
}
@media (max-width:640px){
.jrnl-board .jrnl-board__list { grid-template-columns:1fr; }
}
CSS
);
put_file($skinRoot.'/list.skin.php', <<<'PHP'
', 50);
?>
= 120) $excerpt .= '...';
// 썸네일은 연동 시 get_list_thumbnail()로 교체 권장
$fallback = G5_THEME_URL.'/assets/img/card-placeholder.svg';
?>
PHP
);
// ---------------------------
// 5) ZIP 생성
// ---------------------------
zip_dir($themeRoot, $root.'/theme-journal.zip');
zip_dir($skinRoot, $root.'/skin-journal_board.zip');
header('Content-Type: text/plain; charset=utf-8');
echo "완료!\n";
echo "- 생성: theme/journal\n";
echo "- 생성: theme-journal.zip\n";
echo "- 생성: skin-journal_board.zip\n";
echo "\n주의: install_journal_theme.php 파일은 실행 후 삭제하세요.\n";
} catch (Exception $e) {
header('Content-Type: text/plain; charset=utf-8');
echo "오류: ".$e->getMessage()."\n";
}