extend를 이용하면 금방 만듭니다.
1. extend/top/00_intro.php 파일을 만듭니다.
00은 아주 중요해요. 맨 처음에 실행해야 함으로 메뉴얼을 참고하시면 됩니다.
<?php
/**
* DXCMS — 인트로 페이지
* extend/top/00_intro.php
*
* 동작:
* - 홈(/) 접속 시에만 실행
* - 쿠키 dx_intro_closed=1 이 없으면 intro.php 출력 후 exit
* - 닫기 버튼 또는 24시간 체크박스로 쿠키 설정 → 이후 24시간 비표시
* - CMS 코드 일절 수정 없음
*/
if (!defined('DX_CMS')) exit;
/* ── 홈 여부 확인 ── */
$_introUri = isset($_SERVER['REQUEST_URI']) ? strtok($_SERVER['REQUEST_URI'], '?') : '/';
$_introUri = rtrim($_introUri, '/');
if ($_introUri !== '' && $_introUri !== '/') return; /* 홈이 아니면 스킵 */
/* ── 쿠키 확인 — 24시간 비표시 설정 여부 ── */
if (!empty($_COOKIE['dx_intro_closed'])) return; /* 쿠키 있으면 스킵 */
/* ── 인트로 출력 ── */
$_introFile = DX_ROOT . '/intro.php';
if (!file_exists($_introFile)) return; /* intro.php 없으면 스킵 */
/* CMS 레이아웃 없이 순수 HTML 출력 */
ob_end_clean(); /* 이미 시작된 버퍼 비우기 */
readfile($_introFile);
exit;
2. intro.php 루트에 만들어주세요.
디자인은 여러분들이 알아서 ^^
여기서 중요한 것은 쿠키겠죠.
스크립트는 올려놓겠습니다. 분석은 여러분의 몫
<script>
gsap.registerPlugin(TextPlugin);
/* Three.js 배경 */
const canvas = document.getElementById('webgl-canvas');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.z = 1000;
const renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const particleCount = 4000;
const geometry = new THREE.BufferGeometry();
const positions = [], colors = [];
const colorTeal = new THREE.Color(0x2dd4bf);
const colorIndigo = new THREE.Color(0x818cf8);
for (let i = 0; i < particleCount; i++) {
positions.push(Math.random()*2000-1000, Math.random()*2000-1000, Math.random()*2000-1000);
const mc = colorTeal.clone().lerp(colorIndigo, Math.random());
colors.push(mc.r, mc.g, mc.b);
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({ size:2, vertexColors:true, transparent:true, opacity:.8, sizeAttenuation:true });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
let speedMultiplier = 1;
function animateBg() {
requestAnimationFrame(animateBg);
const pos = particles.geometry.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
pos[i*3+2] += (2.5 * speedMultiplier);
if (pos[i*3+2] > 1000) pos[i*3+2] = -1000;
}
particles.geometry.attributes.position.needsUpdate = true;
particles.rotation.z += 0.001;
renderer.render(scene, camera);
}
animateBg();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
/* GSAP 시퀀스 */
function createSequence(id, typeText) {
const tl = gsap.timeline();
tl.set(id, { z:-2500, opacity:0, scale:.3 })
.to(id, { z:0, opacity:1, scale:1, duration:1.5, ease:"power3.out" })
.to(`${id} .typed-text`, { duration:1.5, text:typeText, ease:"none" })
.to(id, { z:1500, opacity:0, scale:3, duration:.6, ease:"power2.in" }, "+=1.5");
return tl;
}
const masterTimeline = gsap.timeline({
onComplete: function() {
/* 애니메이션 완료 후 닫기/24시간/시작 버튼 표시 */
document.getElementById('intro-close-btn').style.opacity = '1';
document.getElementById('intro-no-show-wrap').style.opacity = '1';
var btnWrap = document.querySelector('.intro-btn-wrap');
if (btnWrap) btnWrap.style.opacity = '1';
}
});
masterTimeline.to(window, { speedMultiplier:5, duration:2, ease:"power2.inOut" });
masterTimeline.add(createSequence("#seq-1", "단일 코드베이스로 실현하는 압도적 확장성 (LGPL v3.0)"), "+=0.2");
masterTimeline.add(createSequence("#seq-2", "PHP 5.6부터 8.x까지 광범위한 호환성 및 멀티도메인 환경 지원"), "+=0.2");
masterTimeline.add(createSequence("#seq-3", "대규모 접속 대응 및 하이브리드 캐싱 (DxCache) 추상화 레이어"), "+=0.2");
masterTimeline.add(createSequence("#seq-4", "OG Tag, JSON-LD 생성 최적화 및 CSRF, XSS 방어 아키텍처"), "+=0.2");
masterTimeline.to(window, { speedMultiplier:15, duration:1.5, ease:"power1.in" }, "+=0.2");
masterTimeline.to("#flash", { opacity:1, duration:.1, ease:"none" })
.to("#flash", { opacity:0, duration:2.0, ease:"power3.out" });
masterTimeline.to(window, { speedMultiplier:2, duration:3.0, ease:"power2.out" }, "<");
masterTimeline.set("#final-ui", { opacity:1 }, "<");
masterTimeline.fromTo(".version-badge", { y:30, opacity:0 }, { y:0, opacity:1, duration:1, ease:"back.out(1.5)" }, "<");
masterTimeline.fromTo(".main-title", { z:-800, opacity:0, scale:.7 }, { z:0, opacity:1, scale:1, duration:2, ease:"elastic.out(1,.4)" }, "-=0.5");
masterTimeline.fromTo(".sub-title", { y:20, opacity:0 }, { y:0, opacity:1, duration:1, ease:"power3.out" }, "-=1.0");
masterTimeline.fromTo(".time-box", { y:40, opacity:0, scale:.8 }, { y:0, opacity:1, scale:1, duration:.6, stagger:.15, ease:"back.out(1.5)" }, "-=0.6");
masterTimeline.fromTo(".copyright", { y:15, opacity:0 }, { y:0, opacity:1, duration:1, ease:"power2.out" }, "-=0.2");
/* 카운트다운 */
const launchDate = new Date('2026-06-15T00:00:00').getTime();
const updateCountdown = () => {
const distance = launchDate - new Date().getTime();
if (distance < 0) {
['days','hours','minutes','seconds'].forEach(id => document.getElementById(id).innerText='00');
return;
}
document.getElementById('days').innerText = String(Math.floor(distance/(1000*60*60*24))).padStart(2,'0');
document.getElementById('hours').innerText = String(Math.floor((distance%(1000*60*60*24))/(1000*60*60))).padStart(2,'0');
document.getElementById('minutes').innerText = String(Math.floor((distance%(1000*60*60))/(1000*60))).padStart(2,'0');
document.getElementById('seconds').innerText = String(Math.floor((distance%(1000*60))/1000)).padStart(2,'0');
};
setInterval(updateCountdown, 1000);
updateCountdown();
/* ── 닫기 / 24시간 쿠키 ── */
function dxIntroSetCookie(hours) {
var exp = new Date();
exp.setTime(exp.getTime() + hours * 60 * 60 * 1000);
document.cookie = 'dx_intro_closed=1; expires=' + exp.toUTCString() + '; path=/';
}
function dxIntroClose() {
var noShow = document.getElementById('intro-no-show');
if (noShow && noShow.checked) {
/* 24시간 쿠키 */
dxIntroSetCookie(24);
} else {
/* 체크 안 했으면 세션 쿠키 (브라우저 닫을 때까지) */
document.cookie = 'dx_intro_closed=1; path=/';
}
/* 부드럽게 페이드아웃 후 이동 */
document.body.style.transition = 'opacity .4s';
document.body.style.opacity = '0';
setTimeout(function() {
window.location.href = window.location.href;
}, 420);
}
function dxIntroCookieAndGo(url) {
/* 닫기와 동일 — 세션 쿠키 설정 후 해당 URL로 이동 */
document.cookie = 'dx_intro_closed=1; path=/';
document.body.style.transition = 'opacity .4s';
document.body.style.opacity = '0';
setTimeout(function() { window.location.href = url; }, 420);
}
function dxIntroNoShowChange(cb) {
/* 체크만 해도 즉시 쿠키 설정 — 브라우저 닫아도 유지 */
if (cb.checked) {
dxIntroSetCookie(24);
} else {
/* 체크 해제 시 쿠키 삭제 */
document.cookie = 'dx_intro_closed=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
}
}
</script>