수정코드

쵸코 @momochi
2026-06-22 02:22
<div class="ra0-content"><details class="fold">
<summary>bgm.css</summary>
<div class="fold-content">/* BGM Player Styles */
<br>.bgm-player {
<br>    position: fixed;
<br>    bottom: var(--spacing-lg);
<br>    right: var(--spacing-lg);
<br>    width: 280px;
<br>    background: var(--bg-glass);
<br>    backdrop-filter: blur(16px);
<br>    border: 1px solid var(--bg-glass-dark);
<br>    border-radius: var(--container-border-radius);
<br>    padding: var(--spacing-lg);
<br>    box-shadow: var(--shadow-glass);
<br>    z-index: 9999;
<br>    font-family: var(--f-pre);
<br>    transition: all var(--transition-base);
<br>    transform: translateY(100px);
<br>    opacity: 0;
<br>    pointer-events: auto; /* iframe 내부에서도 클릭 가능 */
<br>}
<br>
<br>.bgm-player.show {
<br>    transform: translateY(0);
<br>    opacity: 1;
<br>}
<br>
<br>/* 최소화 상태 */
<br>.bgm-player.minimized {
<br>    width: 30px;
<br>    height: 30px;
<br>    padding: 0;
<br>    border-radius: var(--spacing-sm);
<br>    cursor: pointer;
<br>    background: var(--bg-glass);
<br>    border: 1px solid var(--bg-glass-dark);
<br>    display: flex;
<br>    align-items: center;
<br>    justify-content: center;
<br>    transition: all var(--transition-base);
<br>}
<br>
<br>.bgm-player.minimized:hover {
<br>    transform: scale(1.05);
<br>    box-shadow: var(--shadow-lg);
<br>}
<br>
<br>.bgm-player.minimized .player-content {
<br>    display: none;
<br>}
<br>
<br>.bgm-player.minimized::before {
<br>    content: '♪';
<br>    font-size: 1.25rem;
<br>    color: var(--content-font-color);
<br>}
<br>
<br>/* 최소화 상태에서 클릭 가능한 오버레이 */
<br>.minimized-overlay {
<br>    position: absolute;
<br>    top: 0;
<br>    left: 0;
<br>    right: 0;
<br>    bottom: 0;
<br>    border-radius: inherit;
<br>    display: none;
<br>}
<br>
<br>.bgm-player.minimized .minimized-overlay {
<br>    display: block;
<br>}
<br>
<br>/* 노래 제목 + 토글 버튼 헤더 */
<br>.track-header {
<br>    display: flex;
<br>    align-items: center;
<br>    justify-content: space-between;
<br>    gap: var(--spacing-sm);
<br>}
<br>
<br>.track-info {
<br>    color: var(--content-font-color);
<br>    font-size: 12px;
<br>    font-weight: 400;
<br>    font-family: var(--f-pre);
<br>    overflow: hidden;
<br>    text-overflow: ellipsis;
<br>    white-space: nowrap;
<br>    flex: 1;
<br>    line-height: 1.4;
<br>}
<br>
<br>.list-toggle-btn {
<br>    background: var(--bg-secondary);
<br>    border: 1px solid var(--bg-glass-dark);
<br>    color: var(--text-secondary);
<br>    padding: var(--spacing-xs) var(--spacing-sm);
<br>    border-radius: var(--btn-primary-radius);
<br>    cursor: pointer;
<br>    font-size: 11px;
<br>    font-family: var(--f-pre);
<br>    transition: all var(--transition-fast);
<br>    display: flex;
<br>    align-items: center;
<br>    gap: var(--spacing-xxs);
<br>    min-width: 20px;
<br>    justify-content: center;
<br>}
<br>
<br>.list-toggle-btn:hover {
<br>    background: var(--primary-light);
<br>    color: var(--primary-color);
<br>    border-color: var(--primary-color);
<br>    transform: translateY(-1px);
<br>}
<br>
<br>.list-toggle-btn.active {
<br>    background: var(--primary-color);
<br>    color: var(--white);
<br>    border-color: var(--primary-color);
<br>}
<br>
<br>.toggle-icon {
<br>    transition: transform var(--transition-fast);
<br>    font-size: 10px;
<br>}
<br>
<br>.list-toggle-btn.active .toggle-icon {
<br>    transform: rotate(180deg);
<br>}
<br>
<br>/* 플레이리스트 컨테이너 */
<br>.playlist-container {
<br>    max-height: 160px;
<br>    overflow-y: auto;
<br>    margin-top: var(--spacing-md);
<br>    border-radius: var(--card-border-radius);
<br>    background: var(--bg-glass-dark);
<br>    border: 1px solid var(--bg-glass-dark);
<br>}
<br>
<br>.playlist {
<br>    list-style: none;
<br>    padding: 0;
<br>    margin: 0;
<br>}
<br>
<br>.playlist li {
<br>    padding: var(--spacing-sm) var(--spacing-md);
<br>    color: var(--text-secondary);
<br>    font-size: 12px;
<br>    font-family: var(--f-pre);
<br>    cursor: pointer;
<br>    transition: all var(--transition-fast);
<br>    overflow: hidden;
<br>    text-overflow: ellipsis;
<br>    white-space: nowrap;
<br>}
<br>
<br>.playlist li:last-child {
<br>    border-bottom: none;
<br>}
<br>
<br>.playlist li:hover {
<br>    background: var(--primary-light);
<br>    color: var(--primary-color);
<br>}
<br>
<br>.playlist li.current {
<br>    background: var(--primary-color);
<br>    color: var(--white);
<br>    font-weight: 500;
<br>}
<br>
<br>/* 플레이어 컨트롤 */
<br>.player-controls {
<br>    display: flex;
<br>    justify-content: center;
<br>    align-items: center;
<br>    gap: var(--spacing-lg);
<br>}
<br>
<br>.bgm-controls {
<br>    display: flex;
<br>    justify-content: space-between;
<br>    align-items: center;
<br>    margin-bottom: var(--spacing-md);
<br>    padding-right: var(--spacing-sm);
<br>}
<br>
<br>.control-btn {
<br>    background: transparent;
<br>    border: none;
<br>    color: rgb(from var(--primary-color) r g b / 60%);
<br>    font-size: 14px;
<br>    cursor: pointer;
<br>    padding: 0;
<br>    transition: all var(--transition-fast);
<br>    display: flex;
<br>    align-items: center;
<br>    justify-content: center;
<br>    gap: var(--spacing-sm);
<br>}
<br>
<br>.control-btn:hover:not(:disabled) {
<br>    color: rgb(from var(--white) r g b / 30%);
<br>    transform: translateY(-2px);
<br>}
<br>
<br>.control-btn.play-pause {
<br>    background: transparent;
<br>    color: var(--accent-color);
<br>    border: none;
<br>    font-size: 16px;
<br>}
<br>
<br>.control-btn.play-pause:hover {
<br>    transform: translateY(-2px) scale(1.05);
<br>}
<br>
<br>.control-btn:disabled {
<br>    opacity: 0.4;
<br>    cursor: not-allowed;
<br>    transform: none;
<br>}
<br>
<br>.control-btn:disabled:hover {
<br>    transform: none;
<br>    box-shadow: none;
<br>}
<br>
<br>/* 모드 컨트롤 (셔플/반복) */
<br>.mode-controls {
<br>    display: flex;
<br>    align-items: center;
<br>    gap: var(--spacing-sm);
<br>}
<br>
<br>.control-btn.mode-btn {
<br>    font-size: 12px;
<br>    opacity: 0.5;
<br>    position: relative;
<br>}
<br>
<br>.control-btn.mode-btn:hover {
<br>    opacity: 0.8;
<br>}
<br>
<br>.control-btn.mode-btn.active {
<br>    opacity: 1;
<br>    color: var(--accent-color);
<br>}
<br>
<br>/* 1곡 반복 배지 */
<br>.repeat-one-badge {
<br>    position: absolute;
<br>    top: -4px;
<br>    right: -6px;
<br>    font-size: 9px;
<br>    font-weight: 700;
<br>    color: var(--accent-color);
<br>    line-height: 1;
<br>}
<br>
<br>/* 볼륨 컨트롤 */
<br>.volume-control {
<br>    position: relative;
<br>    display: flex;
<br>    align-items: center;
<br>}
<br>
<br>.volume-btn {
<br>    font-size: 14px;
<br>}
<br>
<br>.volume-btn.muted i::before {
<br>    content: "\f6a9"; /* fa-volume-xmark */
<br>}
<br>
<br>.volume-popup {
<br>    position: absolute;
<br>    bottom: 100%;
<br>    left: 50%;
<br>    transform: translateX(-50%);
<br>    background: var(--bg-glass);
<br>    backdrop-filter: blur(16px);
<br>    border: 1px solid var(--bg-glass-dark);
<br>    border-radius: var(--card-border-radius);
<br>    padding: var(--spacing-md) var(--spacing-sm);
<br>    box-shadow: var(--shadow-lg);
<br>    display: none;
<br>    margin-bottom: var(--spacing-sm);
<br>}
<br>
<br>.volume-popup.show {
<br>    display: block;
<br>}
<br>
<br>.volume-slider {
<br>    width: 4px;
<br>    height: 80px;
<br>    background: var(--border-medium);
<br>    border-radius: 2px;
<br>    outline: none;
<br>    -webkit-appearance: slider-vertical;
<br>    appearance: slider-vertical;
<br>    cursor: pointer;
<br>    transition: all var(--transition-fast);
<br>    writing-mode: vertical-lr;
<br>    direction: rtl;
<br>}
<br>
<br>.volume-slider:hover {
<br>    background: var(--primary-light);
<br>}
<br>
<br>.volume-slider::-webkit-slider-thumb {
<br>    -webkit-appearance: none;
<br>    width: 14px;
<br>    height: 14px;
<br>    background: var(--primary-color);
<br>    border-radius: 50%;
<br>    cursor: pointer;
<br>    border: 2px solid var(--white);
<br>    box-shadow: var(--shadow-sm);
<br>    transition: all var(--transition-fast);
<br>}
<br>
<br>.volume-slider::-webkit-slider-thumb:hover {
<br>    transform: scale(1.2);
<br>    box-shadow: var(--shadow-md);
<br>}
<br>
<br>.volume-slider::-moz-range-thumb {
<br>    width: 14px;
<br>    height: 14px;
<br>    background: var(--primary-color);
<br>    border-radius: 50%;
<br>    cursor: pointer;
<br>    border: 2px solid var(--white);
<br>    box-shadow: var(--shadow-sm);
<br>}
<br>
<br>/* 최소화 버튼 */
<br>.minimize-btn {
<br>    position: absolute;
<br>    top: calc(var(--spacing-sm) * 0.8);
<br>    right: calc(var(--spacing-sm) * 0.8);
<br>    /* background: rgb(from var(--accent-color) r g b / 70%); */
<br>    background: transparent;
<br>    background: var(--accent-color);
<br>    /* border: 1px solid var(--bg-glass-dark); */
<br>    border: none;
<br>    color: var(--white);
<br>    font-size: 12px;
<br>    cursor: pointer;
<br>    padding: 0;
<br>    border-radius: var(--btn-primary-radius);
<br>    transition: all var(--transition-fast);
<br>    display: flex;
<br>    align-items: center;
<br>    justify-content: center;
<br>    font-family: var(--f-pre);
<br>}
<br>
<br>.minimize-btn:hover {
<br>    background: var(--primary-color);
<br>    transform: scale(1.1);
<br>}
<br>
<br>/* 스크롤바 스타일 */
<br>.playlist-container::-webkit-scrollbar {
<br>    width: 4px;
<br>}
<br>
<br>.playlist-container::-webkit-scrollbar-track {
<br>    background: var(--gray-100);
<br>    border-radius: 2px;
<br>}
<br>
<br>.playlist-container::-webkit-scrollbar-thumb {
<br>    background: var(--primary-color);
<br>    border-radius: 2px;
<br>    transition: all var(--transition-fast);
<br>}
<br>
<br>.playlist-container::-webkit-scrollbar-thumb:hover {
<br>    background: var(--primary-dark);
<br>}
<br>
<br>/* 애니메이션 */
<br>@keyframes slideInUp {
<br>    from {
<br>        transform: translateY(100px);
<br>        opacity: 0;
<br>    }
<br>    to {
<br>        transform: translateY(0);
<br>        opacity: 1;
<br>    }
<br>}
<br>
<br>@keyframes pulse {
<br>    0% { transform: scale(1); }
<br>    50% { transform: scale(1.05); }
<br>    100% { transform: scale(1); }
<br>}
<br>
<br>.bgm-player.minimized:hover {
<br>    animation: pulse 0.6s ease infinite;
<br>}
<br>
<br>/* 로딩 상태 */
<br>.track-info.loading::after {
<br>    content: '';
<br>    display: inline-block;
<br>    width: 12px;
<br>    height: 12px;
<br>    margin-left: var(--spacing-xs);
<br>    border: 2px solid var(--border-light);
<br>    border-radius: 50%;
<br>    border-top-color: var(--primary-color);
<br>    animation: spin 1s linear infinite;
<br>}
<br>
<br>@keyframes spin {
<br>    to {
<br>        transform: rotate(360deg);
<br>    }
<br>}
<br>
<br>/* 포커스 상태 */
<br>.control-btn:focus,
<br>.list-toggle-btn:focus,
<br>.minimize-btn:focus {
<br>    outline: 2px solid var(--primary-color);
<br>    outline-offset: 2px;
<br>}
<br>
<br>.volume-slider:focus {
<br>    outline: 2px solid var(--primary-color);
<br>    outline-offset: 2px;
<br>}
<br>
<br>/* 접근성 개선 */
<br>@media (prefers-reduced-motion: reduce) {
<br>    .bgm-player,
<br>    .control-btn,
<br>    .list-toggle-btn,
<br>    .minimize-btn,
<br>    .volume-slider::-webkit-slider-thumb,
<br>    .toggle-icon {
<br>        transition: none;
<br>    }
<br>   
<br>    .bgm-player.minimized:hover {
<br>        animation: none;
<br>    }
<br>}
<br>
<br>/* 고해상도 디스플레이 대응 */
<br>@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
<br>    .bgm-player {
<br>        backdrop-filter: blur(20px);
<br>    }
<br>}&nbsp;<br></div>
</details><details class="fold">
<summary>bgm.js</summary>
<div class="fold-content">let player;
<br>let playerReady = false;
<br>let isPlaying = false;
<br>let currentVolume = 30;
<br>let isMinimized = false;
<br>let userInteracted = false;
<br>let readyToPlay = false;
<br>let isPlaylistOpen = false;
<br>let playlistData = ☐;
<br>let currentTrackIndex = 0;
<br>
<br>// 셔플/반복 관련 변수
<br>let isShuffleOn = false;
<br>let repeatMode = 'all'; // 'all', 'one', 'none'
<br>let shuffledOrder = ☐; // 셔플된 인덱스 순서
<br>let shuffledIndex = 0;  // 셔플 순서에서의 현재 위치
<br>let isVolumeOpen = false;
<br>
<br>// localStorage 키
<br>const BGM_STORAGE_KEY = 'ra0_bgm_settings';
<br>
<br>// localStorage 저장
<br>function saveBGMSettings() {
<br>    try {
<br>        const settings = {
<br>            volume: currentVolume,
<br>            shuffle: isShuffleOn,
<br>            repeat: repeatMode
<br>        };
<br>        localStorage.setItem(BGM_STORAGE_KEY, JSON.stringify(settings));
<br>    } catch (e) {
<br>    }
<br>}
<br>
<br>// localStorage 복원
<br>function loadBGMSettings() {
<br>    try {
<br>        const saved = localStorage.getItem(BGM_STORAGE_KEY);
<br>        if (saved) {
<br>            const settings = JSON.parse(saved);
<br>            if (typeof settings.volume === 'number') {
<br>                currentVolume = settings.volume;
<br>            }
<br>            if (typeof settings.shuffle === 'boolean') {
<br>                isShuffleOn = settings.shuffle;
<br>            }
<br>            if (['all', 'one', 'none'].includes(settings.repeat)) {
<br>                repeatMode = settings.repeat;
<br>            }
<br>        }
<br>    } catch (e) {
<br>    }
<br>}
<br>
<br>// 셔플 순서 생성
<br>function generateShuffledOrder() {
<br>    if (!playlistData.length) return;
<br>
<br>    shuffledOrder = [...Array(playlistData.length).keys()];
<br>    // Fisher-Yates 셔플
<br>    for (let i = shuffledOrder.length - 1; i &gt; 0; i--) {
<br>        const j = Math.floor(Math.random() * (i + 1));
<br>        [shuffledOrder[i], shuffledOrder[j]] = [shuffledOrder[j], shuffledOrder[i]];
<br>    }
<br>
<br>    // 현재 곡을 첫 번째로 이동
<br>    const currentPos = shuffledOrder.indexOf(currentTrackIndex);
<br>    if (currentPos &gt; 0) {
<br>        shuffledOrder.splice(currentPos, 1);
<br>        shuffledOrder.unshift(currentTrackIndex);
<br>    }
<br>    shuffledIndex = 0;
<br>}
<br>
<br>// 셔플 토글
<br>function toggleShuffle() {
<br>    isShuffleOn = !isShuffleOn;
<br>
<br>    if (isShuffleOn) {
<br>        generateShuffledOrder();
<br>    }
<br>
<br>    updateShuffleUI();
<br>    saveBGMSettings();
<br>}
<br>
<br>// 반복 모드 순환
<br>function cycleRepeatMode() {
<br>    if (repeatMode === 'all') {
<br>        repeatMode = 'one';
<br>    } else if (repeatMode === 'one') {
<br>        repeatMode = 'none';
<br>    } else {
<br>        repeatMode = 'all';
<br>    }
<br>
<br>    updateRepeatUI();
<br>    saveBGMSettings();
<br>}
<br>
<br>// 셔플 UI 업데이트
<br>function updateShuffleUI() {
<br>    const shuffleBtn = document.getElementById('shuffle-btn');
<br>    if (shuffleBtn) {
<br>        if (isShuffleOn) {
<br>            shuffleBtn.classList.add('active');
<br>        } else {
<br>            shuffleBtn.classList.remove('active');
<br>        }
<br>    }
<br>}
<br>
<br>// 반복 UI 업데이트
<br>function updateRepeatUI() {
<br>    const repeatBtn = document.getElementById('repeat-btn');
<br>    if (!repeatBtn) return;
<br>
<br>    repeatBtn.classList.remove('active', 'repeat-one');
<br>
<br>    if (repeatMode === 'all') {
<br>        repeatBtn.classList.add('active');
<br>        repeatBtn.innerHTML = '&lt;i class="fa-solid fa-repeat"&gt;&lt;/i&gt;';
<br>    } else if (repeatMode === 'one') {
<br>        repeatBtn.classList.add('active', 'repeat-one');
<br>        repeatBtn.innerHTML = '&lt;i class="fa-solid fa-repeat"&gt;&lt;/i&gt;&lt;span class="repeat-one-badge"&gt;1&lt;/span&gt;';
<br>    } else {
<br>        repeatBtn.innerHTML = '&lt;i class="fa-solid fa-repeat"&gt;&lt;/i&gt;';
<br>    }
<br>}
<br>
<br>// 볼륨 팝업 토글
<br>function toggleVolume() {
<br>    isVolumeOpen = !isVolumeOpen;
<br>    const popup = document.getElementById('volume-popup');
<br>    if (popup) {
<br>        if (isVolumeOpen) {
<br>            popup.classList.add('show');
<br>        } else {
<br>            popup.classList.remove('show');
<br>        }
<br>    }
<br>}
<br>
<br>// 볼륨 아이콘 업데이트
<br>function updateVolumeIcon() {
<br>    const volumeBtn = document.getElementById('volume-btn');
<br>    if (!volumeBtn) return;
<br>
<br>    let icon = 'fa-volume-high';
<br>    if (currentVolume == 0) {
<br>        icon = 'fa-volume-xmark';
<br>    } else if (currentVolume &lt; 30) {
<br>        icon = 'fa-volume-off';
<br>    } else if (currentVolume &lt; 70) {
<br>        icon = 'fa-volume-low';
<br>    }
<br>
<br>    volumeBtn.innerHTML = `&lt;i class="fa-solid ${icon}"&gt;&lt;/i&gt;`;
<br>}
<br>
<br>// Pixel 스킨 볼륨 바 업데이트 함수
<br>function updatePixelVolumeBar(volume) {
<br>    const pixelVolBar = document.getElementById('pixel-vol-bar');
<br>    if (!pixelVolBar) return;
<br>
<br>    const segments = pixelVolBar.querySelectorAll('.vol-seg');
<br>    const filledCount = Math.round(volume / 10); // 0-100 → 0-10
<br>
<br>    segments.forEach((seg, index) =&gt; {
<br>        if (index &lt; filledCount) {
<br>            seg.classList.add('filled');
<br>        } else {
<br>            seg.classList.remove('filled');
<br>        }
<br>    });
<br>}
<br>
<br>// 다음 트랙 인덱스 계산 (셔플/반복 고려)
<br>function getNextTrackIndex() {
<br>    if (playlistData.length &lt;= 1) return 0;
<br>
<br>    if (repeatMode === 'one') {
<br>        return currentTrackIndex;
<br>    }
<br>
<br>    if (isShuffleOn) {
<br>        shuffledIndex++;
<br>        if (shuffledIndex &gt;= shuffledOrder.length) {
<br>            if (repeatMode === 'none') {
<br>                return -1; // 재생 중지
<br>            }
<br>            shuffledIndex = 0;
<br>            generateShuffledOrder(); // 새로운 셔플 순서
<br>        }
<br>        return shuffledOrder[shuffledIndex];
<br>    } else {
<br>        const nextIndex = currentTrackIndex + 1;
<br>        if (nextIndex &gt;= playlistData.length) {
<br>            if (repeatMode === 'none') {
<br>                return -1; // 재생 중지
<br>            }
<br>            return 0;
<br>        }
<br>        return nextIndex;
<br>    }
<br>}
<br>
<br>// 이전 트랙 인덱스 계산 (셔플/반복 고려)
<br>function getPrevTrackIndex() {
<br>    if (playlistData.length &lt;= 1) return 0;
<br>
<br>    if (isShuffleOn) {
<br>        shuffledIndex--;
<br>        if (shuffledIndex &lt; 0) {
<br>            shuffledIndex = shuffledOrder.length - 1;
<br>        }
<br>        return shuffledOrder[shuffledIndex];
<br>    } else {
<br>        return currentTrackIndex &gt; 0 ? currentTrackIndex - 1 : playlistData.length - 1;
<br>    }
<br>}
<br>
<br>// 보안 검증 함수들
<br>function validateOrigin() {
<br>    // 현재 실행 중인 도메인은 항상 허용 (자동 감지)
<br>    const currentOrigin = window.location.hostname;
<br>
<br>    // localhost 또는 실제 도메인(점이 포함된 hostname)이면 허용
<br>    if (currentOrigin === 'localhost' || currentOrigin.includes('.')) {
<br>        return true;
<br>    }
<br>
<br>    // 그 외에는 거부 (비정상적인 경우)
<br>    return false;
<br>}
<br>
<br>// 보안 YouTube API 준비
<br>function onYouTubeIframeAPIReady() {
<br>    if (!validateOrigin()) {
<br>        return;
<br>    }
<br>   
<br>    initBGMPlayer();
<br>}
<br>
<br>async function initBGMPlayer() {
<br>    // 추가 보안 검증
<br>    if (!validateOrigin()) {
<br>        return;
<br>    }
<br>   
<br>    if (!bgmConfig.youtube) {
<br>        return;
<br>    }
<br>   
<br>    // YouTube Player 생성 (보안 강화된 설정)
<br>    let playerDiv = document.getElementById('youtube-player');
<br>    if (!playerDiv) {
<br>        playerDiv = document.createElement('div');
<br>        playerDiv.id = 'youtube-player';
<br>        playerDiv.style.display = 'none';
<br>        document.body.appendChild(playerDiv);
<br>    }
<br>   
<br>    // 보안 강화된 playerVars
<br>    // iframe 내부에서 실행될 때 최상위 페이지의 origin 사용
<br>    let pageOrigin;
<br>    try {
<br>        pageOrigin = window.top.location.origin;
<br>    } catch(e) {
<br>        // cross-origin인 경우 현재 origin 사용
<br>        pageOrigin = window.location.origin;
<br>    }
<br>
<br>    const securePlayerVars = {
<br>        autoplay: 0,
<br>        controls: 0,
<br>        showinfo: 0,
<br>        rel: 0,
<br>        iv_load_policy: 3,
<br>        enablejsapi: 1,
<br>        modestbranding: 1,
<br>        fs: 0,
<br>        cc_load_policy: 0,
<br>        disablekb: 1,
<br>        origin: pageOrigin
<br>    };
<br>   
<br>    if (bgmConfig.youtube.type === 'playlist') {
<br>        player = new YT.Player('youtube-player', {
<br>            height: '0',
<br>            width: '0',
<br>            playerVars: {
<br>                ...securePlayerVars,
<br>                listType: 'playlist',
<br>                list: bgmConfig.youtube.id
<br>            },
<br>            events: {
<br>                'onReady': onPlayerReady,
<br>                'onStateChange': onPlayerStateChange,
<br>                'onError': onPlayerError
<br>            }
<br>        });
<br>    } else {
<br>        player = new YT.Player('youtube-player', {
<br>            height: '0',
<br>            width: '0',
<br>            videoId: bgmConfig.youtube.id,
<br>            playerVars: securePlayerVars,
<br>            events: {
<br>                'onReady': onPlayerReady,
<br>                'onStateChange': onPlayerStateChange,
<br>                'onError': onPlayerError
<br>            }
<br>        });
<br>    }
<br>}
<br>
<br>function onPlayerReady(event) {
<br>
<br>    playerReady = true;
<br>    readyToPlay = true;
<br>
<br>    // 저장된 설정 복원
<br>    loadBGMSettings();
<br>
<br>    player.setVolume(currentVolume);
<br>
<br>    // 볼륨 슬라이더 동기화
<br>    const volumeSlider = document.getElementById('volume-slider');
<br>    if (volumeSlider) {
<br>        volumeSlider.value = currentVolume;
<br>    }
<br>
<br>    // 플레이어 표시
<br>    setTimeout(() =&gt; {
<br>        const bgmPlayer = document.getElementById('bgm-player');
<br>        if (bgmPlayer) {
<br>            bgmPlayer.classList.add('show');
<br>        }
<br>    }, 500);
<br>
<br>    updateTrackInfo();
<br>    updateControls();
<br>    loadPlaylist();
<br>
<br>    // 셔플/반복/볼륨 UI 초기화
<br>    updateShuffleUI();
<br>    updateRepeatUI();
<br>    updateVolumeIcon();
<br>
<br>    // 사용자 상호작용 대기
<br>    setupUserInteractionListeners();
<br>    showBGMNotice();
<br>}
<br>
<br>// 보안 강화된 플레이리스트 로드
<br>async function loadPlaylist() {
<br>    if (!playerReady) return;
<br>   
<br>    try {
<br>        const playlist = player.getPlaylist();
<br>        if (playlist &amp;&amp; playlist.length &gt; 1) {
<br>            const toggleBtn = document.getElementById('list-toggle-btn');
<br>            if (toggleBtn) {
<br>                toggleBtn.style.display = 'flex';
<br>            }
<br>           
<br>            playlistData = playlist;
<br>
<br>            // 셔플이 켜져있으면 셔플 순서 생성
<br>            if (isShuffleOn) {
<br>                generateShuffledOrder();
<br>            }
<br>
<br>            await loadPlaylistTitles();
<br>            updatePlaylistUI();
<br>        }
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>// 보안 강화된 제목 가져오기
<br>async function fetchVideoTitle(videoId) {
<br>    // videoId 검증
<br>    if (!videoId || typeof videoId !== 'string' || !/^[a-zA-Z0-9_-]{11}$/.test(videoId)) {
<br>        return null;
<br>    }
<br>   
<br>    try {
<br>        // 안전한 URL 구성
<br>        const safeUrl = `URL
<br>       
<br>        const response = await fetch(safeUrl, {
<br>            method: 'GET',
<br>            headers: {
<br>                'Accept': 'application/json',
<br>            },
<br>            credentials: 'omit',
<br>            referrerPolicy: 'no-referrer'
<br>        });
<br>       
<br>        if (!response.ok) {
<br>            throw new Error(`HTTP ${response.status}`);
<br>        }
<br>       
<br>        const data = await response.json();
<br>       
<br>        // 응답 데이터 검증
<br>        if (data &amp;&amp; typeof data.title === 'string' &amp;&amp; data.title.trim() !== '') {
<br>            // XSS 방지를 위한 제목 정리
<br>            return data.title.replace(/&lt;[^&gt;]*&gt;/g, '').trim();
<br>        }
<br>       
<br>        return null;
<br>    } catch (error) {
<br>        return null;
<br>    }
<br>}
<br>
<br>// 보안 강화된 사용자 상호작용 리스너
<br>function setupUserInteractionListeners() {
<br>    const events = ['click', 'touchstart', 'keydown', 'scroll', 'mousemove'];
<br>   
<br>    const handleUserInteraction = (e) =&gt; {
<br>        // 이벤트 검증
<br>        if (!e || !e.type) {
<br>            return;
<br>        }
<br>       
<br>        if (!userInteracted) {
<br>            userInteracted = true;
<br>           
<br>            // 재생 전 최종 보안 검증
<br>            if (readyToPlay &amp;&amp; player &amp;&amp; playerReady ) {
<br>                startBGM();
<br>            }
<br>           
<br>            hideBGMNotice();
<br>           
<br>            events.forEach(event =&gt; {
<br>                document.removeEventListener(event, handleUserInteraction, true);
<br>            });
<br>        }
<br>    };
<br>
<br>    events.forEach(event =&gt; {
<br>        document.addEventListener(event, handleUserInteraction, true);
<br>    });
<br>}
<br>
<br>// 보안 강화된 BGM 시작
<br>function startBGM() {
<br>    if (!player || !playerReady) {
<br>        return;
<br>    }
<br>   
<br>    try {
<br>        player.playVideo();
<br>       
<br>        setTimeout(() =&gt; {
<br>            try {
<br>                const newState = player.getPlayerState();
<br>               
<br>                if (newState === YT.PlayerState.PLAYING) {
<br>                    // 재생 성공
<br>                } else if (newState === YT.PlayerState.BUFFERING) {
<br>                    // 버퍼링 중
<br>                } else {
<br>                    // 재생 실패 시 재시도
<br>                    setTimeout(() =&gt; {
<br>                        if (true) {
<br>                            player.playVideo();
<br>                        }
<br>                    }, 1000);
<br>                }
<br>            } catch (error) {
<br>            }
<br>        }, 500);
<br>       
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>// 보안 강화된 플레이어 상태 변경
<br>function onPlayerStateChange(event) {
<br>    const bgmPlayer = document.getElementById('bgm-player');
<br>
<br>    if (event.data === YT.PlayerState.PLAYING) {
<br>        isPlaying = true;
<br>        const playBtn = document.getElementById('play-btn');
<br>        if (playBtn) playBtn.innerHTML = '&lt;i class="fa-solid fa-pause"&gt;&lt;/i&gt;';
<br>        if (bgmPlayer) bgmPlayer.classList.add('playing');
<br>
<br>        setTimeout(() =&gt; {
<br>            updateTrackInfo();
<br>            updateCurrentTrackTitle();
<br>        }, 2000);
<br>
<br>    } else if (event.data === YT.PlayerState.PAUSED) {
<br>        isPlaying = false;
<br>        const playBtn = document.getElementById('play-btn');
<br>        if (playBtn) playBtn.innerHTML = '&lt;i class="fa-solid fa-play"&gt;&lt;/i&gt;';
<br>        if (bgmPlayer) bgmPlayer.classList.remove('playing');
<br>    } else if (event.data === YT.PlayerState.ENDED) {
<br>        // 셔플/반복 모드에 따른 재생 처리
<br>        if (playlistData.length &gt; 1) {
<br>            const nextIndex = getNextTrackIndex();
<br>
<br>            if (nextIndex === -1) {
<br>                // repeatMode === 'none' 이고 마지막 곡인 경우
<br>                isPlaying = false;
<br>                const playBtn = document.getElementById('play-btn');
<br>                if (playBtn) playBtn.innerHTML = '&lt;i class="fa-solid fa-play"&gt;&lt;/i&gt;';
<br>                return;
<br>            }
<br>
<br>            currentTrackIndex = nextIndex;
<br>
<br>            setTimeout(() =&gt; {
<br>                try {
<br>                    player.playVideoAt(currentTrackIndex);
<br>                } catch (error) {
<br>                    player.seekTo(0);
<br>                    player.playVideo();
<br>                }
<br>            }, 500);
<br>
<br>            updatePlaylistUI();
<br>            setTimeout(() =&gt; {
<br>                updateTrackInfo();
<br>                updateCurrentTrackTitle();
<br>            }, 2000);
<br>        } else {
<br>            // 단일 곡
<br>            if (repeatMode === 'none') {
<br>                isPlaying = false;
<br>                const playBtn = document.getElementById('play-btn');
<br>                if (playBtn) playBtn.innerHTML = '&lt;i class="fa-solid fa-play"&gt;&lt;/i&gt;';
<br>                return;
<br>            }
<br>
<br>            // 1곡 반복 또는 전체 반복 (단일곡은 동일)
<br>            setTimeout(() =&gt; {
<br>                try {
<br>                    player.seekTo(0);
<br>                    player.playVideo();
<br>                } catch (error) {
<br>                }
<br>            }, 500);
<br>        }
<br>    }
<br>   
<br>    updateControls();
<br>}
<br>
<br>// 보안 강화된 플레이 토글
<br>function togglePlay() {
<br>    if (!playerReady || !true) return;
<br>
<br>    if (!userInteracted) {
<br>        userInteracted = true;
<br>        hideBGMNotice();
<br>    }
<br>
<br>    try {
<br>        if (isPlaying) {
<br>            player.pauseVideo();
<br>        } else {
<br>            player.playVideo();
<br>        }
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>function stopTrack() {
<br>    if (!playerReady) return;
<br>
<br>    try {
<br>        player.stopVideo();
<br>        isPlaying = false;
<br>        updatePlayPauseButton();
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>// 트랙 이동 함수들 (셔플 지원)
<br>function previousTrack() {
<br>    if (!playerReady || playlistData.length &lt;= 1) return;
<br>
<br>    if (!userInteracted) {
<br>        userInteracted = true;
<br>        hideBGMNotice();
<br>    }
<br>
<br>    try {
<br>        currentTrackIndex = getPrevTrackIndex();
<br>        player.playVideoAt(currentTrackIndex);
<br>        updatePlaylistUI();
<br>        setTimeout(() =&gt; {
<br>            updateTrackInfo();
<br>            updateCurrentTrackTitle();
<br>        }, 2000);
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>function nextTrack() {
<br>    if (!playerReady || playlistData.length &lt;= 1) return;
<br>
<br>    if (!userInteracted) {
<br>        userInteracted = true;
<br>        hideBGMNotice();
<br>    }
<br>
<br>    try {
<br>        const nextIndex = getNextTrackIndex();
<br>        if (nextIndex === -1) {
<br>            // repeatMode === 'none' 마지막 곡
<br>            return;
<br>        }
<br>        currentTrackIndex = nextIndex;
<br>        player.playVideoAt(currentTrackIndex);
<br>        updatePlaylistUI();
<br>        setTimeout(() =&gt; {
<br>            updateTrackInfo();
<br>            updateCurrentTrackTitle();
<br>        }, 2000);
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>async function loadPlaylistTitles() {
<br>    // 보안 체크 제거됨
<br>   
<br>    window.playlistTitles = new Array(playlistData.length).fill(null);
<br>   
<br>    const titlePromises = playlistData.map(async (videoId, index) =&gt; {
<br>        try {
<br>            const title = await fetchVideoTitle(videoId);
<br>            window.playlistTitles[index] = title || `곡 ${index + 1}`;
<br>           
<br>            if (title) {
<br>                updatePlaylistUI();
<br>            }
<br>        } catch (error) {
<br>            window.playlistTitles[index] = `곡 ${index + 1}`;
<br>        }
<br>    });
<br>   
<br>    await Promise.all(titlePromises);
<br>}
<br>
<br>function updatePlaylistUI() {
<br>    const playlistEl = document.getElementById('playlist');
<br>    if (!playlistEl || !playlistData.length) return;
<br>   
<br>    playlistEl.innerHTML = '';
<br>   
<br>    playlistData.forEach((videoId, index) =&gt; {
<br>        const li = document.createElement('li');
<br>       
<br>        let title;
<br>        if (window.playlistTitles &amp;&amp; window.playlistTitles[index]) {
<br>            // XSS 방지
<br>            title = window.playlistTitles[index].replace(/&lt;[^&gt;]*&gt;/g, '');
<br>        } else {
<br>            title = `곡 ${index + 1} (로딩중...)`;
<br>        }
<br>           
<br>        li.textContent = title;
<br>        li.onclick = () =&gt; {
<br>            // 클릭 시 보안 검증
<br>            if (true) {
<br>                playTrack(index);
<br>            }
<br>        };
<br>       
<br>        if (index === currentTrackIndex) {
<br>            li.classList.add('current');
<br>        }
<br>       
<br>        playlistEl.appendChild(li);
<br>    });
<br>}
<br>
<br>function playTrack(index) {
<br>    if (!playerReady || !playlistData.length || !true) return;
<br>   
<br>    try {
<br>        player.playVideoAt(index);
<br>        currentTrackIndex = index;
<br>        updatePlaylistUI();
<br>       
<br>        setTimeout(() =&gt; {
<br>            updateTrackInfo();
<br>            updateCurrentTrackTitle();
<br>        }, 2000);
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>async function updateCurrentTrackTitle() {
<br>    if (!playerReady || !true) return;
<br>   
<br>    try {
<br>        const videoData = player.getVideoData();
<br>       
<br>        if (videoData &amp;&amp; videoData.title &amp;&amp; videoData.title.trim() !== '') {
<br>            if (window.playlistTitles) {
<br>                // XSS 방지
<br>                window.playlistTitles[currentTrackIndex] = videoData.title.replace(/&lt;[^&gt;]*&gt;/g, '');
<br>                updatePlaylistUI();
<br>            }
<br>            return;
<br>        }
<br>       
<br>        if (playlistData[currentTrackIndex]) {
<br>            const title = await fetchVideoTitle(playlistData[currentTrackIndex]);
<br>            if (title &amp;&amp; window.playlistTitles) {
<br>                window.playlistTitles[currentTrackIndex] = title;
<br>                updatePlaylistUI();
<br>            }
<br>        }
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>function togglePlaylist() {
<br>   
<br>   
<br>    const container = document.getElementById('playlist-container');
<br>    const toggleBtn = document.getElementById('list-toggle-btn');
<br>   
<br>    if (!container || !toggleBtn) return;
<br>   
<br>    isPlaylistOpen = !isPlaylistOpen;
<br>   
<br>    if (isPlaylistOpen) {
<br>        container.style.display = 'block';
<br>        toggleBtn.classList.add('active');
<br>    } else {
<br>        container.style.display = 'none';
<br>        toggleBtn.classList.remove('active');
<br>    }
<br>}
<br>
<br>function togglePlayer() {
<br>   
<br>   
<br>    const playerElement = document.getElementById('bgm-player');
<br>    if (!playerElement) return;
<br>   
<br>    isMinimized = !isMinimized;
<br>   
<br>    if (isMinimized) {
<br>        playerElement.classList.add('minimized');
<br>        if (isPlaylistOpen) {
<br>            togglePlaylist();
<br>        }
<br>    } else {
<br>        playerElement.classList.remove('minimized');
<br>    }
<br>}
<br>
<br>function onPlayerError(event) {
<br>    const trackInfo = document.getElementById('track-info');
<br>    if (trackInfo) {
<br>        trackInfo.textContent = '재생 오류 발생';
<br>    }
<br>}
<br>
<br>function showBGMNotice() {
<br>    if (document.getElementById('bgm-notice')) return;
<br>
<br>    const notice = document.createElement('div');
<br>    notice.id = 'bgm-notice';
<br>    notice.innerHTML = `
<br>        &lt;div style="
<br>            position: fixed;
<br>            bottom: 20px;
<br>            left: 20px;
<br>            background: rgba(0,0,0,0.9);
<br>            color: white;
<br>            padding: 12px 16px;
<br>            border-radius: 8px;
<br>            font-size: 13px;
<br>            z-index: 9999;
<br>            cursor: pointer;
<br>            transition: all 0.3s ease;
<br>            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
<br>            backdrop-filter: blur(10px);
<br>            animation: slideIn 0.3s ease;
<br>        " onclick="handleNoticeClick()"&gt;
<br>            페이지를 클릭하면 BGM이 재생됩니다
<br>            &lt;span style="margin-left: 10px; opacity: 0.7; font-size: 11px;"&gt;[클릭하여 닫기]&lt;/span&gt;
<br>        &lt;/div&gt;
<br>        &lt;style&gt;
<br>            @keyframes slideIn {
<br>                from { transform: translateY(100px); opacity: 0; }
<br>                to { transform: translateY(0); opacity: 1; }
<br>            }
<br>        &lt;/style&gt;
<br>    `;
<br>    document.body.appendChild(notice);
<br>
<br>    setTimeout(() =&gt; {
<br>        hideBGMNotice();
<br>    }, 10000);
<br>}
<br>
<br>function handleNoticeClick() {
<br>    if (!userInteracted) {
<br>        userInteracted = true;
<br>       
<br>        if (readyToPlay &amp;&amp; player &amp;&amp; playerReady) {
<br>            startBGM();
<br>        }
<br>    }
<br>    hideBGMNotice();
<br>}
<br>
<br>function hideBGMNotice() {
<br>    const notice = document.getElementById('bgm-notice');
<br>    if (notice) {
<br>        const noticeEl = notice.firstElementChild;
<br>        noticeEl.style.opacity = '0';
<br>        noticeEl.style.transform = 'translateY(20px)';
<br>        setTimeout(() =&gt; {
<br>            if (notice.parentNode) {
<br>                notice.remove();
<br>            }
<br>        }, 300);
<br>    }
<br>}
<br>
<br>function updateTrackInfo() {
<br>    if (!playerReady || !true) return;
<br>
<br>    const trackInfo = document.getElementById('track-info');
<br>    if (!trackInfo) return;
<br>
<br>    try {
<br>        const videoData = player.getVideoData();
<br>
<br>        if (videoData &amp;&amp; videoData.title) {
<br>            // XSS 방지를 위한 제목 정리
<br>            const safeTitle = videoData.title.replace(/&lt;[^&gt;]*&gt;/g, '');
<br>            trackInfo.textContent = safeTitle;
<br>        } else {
<br>            if (playlistData.length &gt; 1) {
<br>                trackInfo.textContent = `곡 ${currentTrackIndex + 1}`;
<br>            } else {
<br>                trackInfo.textContent = '재생 중...';
<br>            }
<br>        }
<br>
<br>        // 썸네일 업데이트
<br>        updateThumbnail();
<br>    } catch (error) {
<br>        if (playlistData.length &gt; 1) {
<br>            trackInfo.textContent = `곡 ${currentTrackIndex + 1}`;
<br>        } else {
<br>            trackInfo.textContent = '재생 중...';
<br>        }
<br>    }
<br>}
<br>
<br>// 썸네일 업데이트 함수
<br>function updateThumbnail() {
<br>    if (!playerReady) return;
<br>
<br>    try {
<br>        const videoData = player.getVideoData();
<br>        let videoId = null;
<br>
<br>        if (videoData &amp;&amp; videoData.video_id) {
<br>            videoId = videoData.video_id;
<br>        } else if (playlistData.length &gt; 0 &amp;&amp; playlistData[currentTrackIndex]) {
<br>            videoId = playlistData[currentTrackIndex];
<br>        }
<br>
<br>        if (videoId) {
<br>            const thumbnailUrl = `URL
<br>
<br>            // CD 스킨 썸네일
<br>            const cdThumb = document.getElementById('cd-thumbnail');
<br>            if (cdThumb) {
<br>                cdThumb.src = thumbnailUrl;
<br>            }
<br>
<br>            // Vinyl 스킨 썸네일
<br>            const vinylThumb = document.getElementById('vinyl-thumbnail');
<br>            if (vinylThumb) {
<br>                vinylThumb.src = thumbnailUrl;
<br>            }
<br>
<br>            // Neon 스킨 썸네일
<br>            const neonThumb = document.getElementById('neon-thumbnail');
<br>            if (neonThumb) {
<br>                neonThumb.src = thumbnailUrl;
<br>            }
<br>
<br>            // Pixel 스킨 썸네일
<br>            const pixelThumb = document.getElementById('pixel-thumbnail');
<br>            if (pixelThumb) {
<br>                pixelThumb.src = thumbnailUrl;
<br>            }
<br>        }
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>function updateControls() {
<br>    if (!playerReady || !true) return;
<br>   
<br>    try {
<br>        const playlist = player.getPlaylist();
<br>        const prevBtn = document.getElementById('prev-btn');
<br>        const nextBtn = document.getElementById('next-btn');
<br>       
<br>        if (prevBtn &amp;&amp; nextBtn) {
<br>            if (playlist &amp;&amp; playlist.length &gt; 1) {
<br>                prevBtn.disabled = false;
<br>                nextBtn.disabled = false;
<br>            } else {
<br>                prevBtn.disabled = true;
<br>                nextBtn.disabled = true;
<br>            }
<br>        }
<br>    } catch (error) {
<br>    }
<br>}
<br>
<br>// 보안 강화된 DOM 로드 이벤트
<br>document.addEventListener('DOMContentLoaded', function() {
<br>    // 볼륨 컨트롤 이벤트 (보안 강화)
<br>    const volumeSlider = document.getElementById('volume-slider');
<br>    if (volumeSlider) {
<br>        volumeSlider.addEventListener('input', function() {
<br>            // 볼륨 조절 시 보안 검증
<br>            // 보안 체크 제거됨
<br>           
<br>            if (!userInteracted) {
<br>                userInteracted = true;
<br>                hideBGMNotice();
<br>                if (readyToPlay &amp;&amp; player &amp;&amp; playerReady) {
<br>                    startBGM();
<br>                }
<br>            }
<br>           
<br>            currentVolume = parseInt(this.value);
<br>
<br>            if (playerReady &amp;&amp; player) {
<br>                player.setVolume(currentVolume);
<br>            }
<br>
<br>            updateVolumeIcon();
<br>            saveBGMSettings();
<br>
<br>            // Retro 스킨 볼륨 표시 업데이트
<br>            const volDisplay = document.getElementById('vol-display');
<br>            if (volDisplay) {
<br>                volDisplay.textContent = currentVolume + '%';
<br>            }
<br>
<br>            // Pixel 스킨 볼륨 바 업데이트
<br>            updatePixelVolumeBar(currentVolume);
<br>        });
<br>    }
<br>
<br>    // 초기 픽셀 볼륨 바 설정
<br>    updatePixelVolumeBar(currentVolume);
<br>
<br>    // 외부 클릭 시 볼륨 팝업 닫기
<br>    document.addEventListener('click', function(e) {
<br>        const volumeControl = document.querySelector('.volume-control');
<br>        if (volumeControl &amp;&amp; !volumeControl.contains(e.target) &amp;&amp; isVolumeOpen) {
<br>            isVolumeOpen = false;
<br>            const popup = document.getElementById('volume-popup');
<br>            if (popup) popup.classList.remove('show');
<br>        }
<br>    });
<br>   
<br>    // 보안 YouTube API 로드
<br>    if (typeof YT === 'undefined') {
<br>        const script = document.createElement('script');
<br>        script.src = 'URL';
<br>        script.onload = function() {
<br>            // YouTube API 로드 성공
<br>        };
<br>        script.onerror = function() {
<br>        };
<br>        document.head.appendChild(script);
<br>    }
<br>});
<br>
<br>// 페이지 언로드 시 정리
<br>window.addEventListener('beforeunload', function() {
<br>    if (player &amp;&amp; typeof player.destroy === 'function') {
<br>        try {
<br>            player.destroy();
<br>        } catch (error) {
<br>        }
<br>    }
<br>});&nbsp;<br></div>
</details><details class="fold">
<summary>intro.php</summary>
<div class="fold-content">&lt;?php<br>define('_INTRO_', true);<br>include_once('./_common.php');<br><br>// 로그인한 회원은 메인으로 이동<br>if ($is_member) {<br>$main_link = get_main_link();<br><br>// iframe 내부에서는 PostMessage로 메인 이동<br>if (isset($_GET['iframe_skip'])) {<br>echo "&lt;script&gt;<br>if (window.parent &amp;&amp; window.parent !== window) {<br>window.parent.postMessage({type: 'navigate', url: '".$main_link."'}, '*');<br>} else {<br>location.href = '".$main_link."';<br>}<br>&lt;/script&gt;";<br>exit;<br>}<br>}<br><br>include_once(G5_PATH.'/head.sub.php');<br>?&gt;<br><br>&lt;div class="intro-container"&gt;<br>&lt;div class="site-logo"&gt;<br>&lt;?php<br>$logo_url = $design['logo_image_url'];<br>$use_logo = $design['use_logo'] == '1';<br>$main_link = get_main_link();<br>?&gt;<br><br>&lt;a href="&lt;?=$main_link?&gt;" class="logo-link"&gt;<br>&lt;?php if ($use_logo): ?&gt;<br>&lt;img src="&lt;?php echo htmlspecialchars($design['logo_image_url'] ?? G5_IMG_URL.'/logo.png'); ?&gt;" alt="&lt;?php echo $config['cf_title']; ?&gt;"&gt;<br>&lt;?php else: ?&gt;<br>&lt;span class="logo-text"&gt;&lt;?php echo $config['cf_title']; ?&gt;&lt;/span&gt;<br>&lt;?php endif; ?&gt;<br>&lt;/a&gt;<br>&lt;/div&gt;<br><br>&lt;div class="intro-content"&gt;<br>&lt;?php if ($config['cf_visit'] == '1') { ?&gt;<br>&lt;!-- 비회원: 로그인 폼 (비공개 설정일 때만 표시) --&gt;<br>&lt;div class="login_form"&gt;<br>&lt;form name="flogin" action="&lt;?php echo $login_action_url ?&gt;" onsubmit="return flogin_submit(this);" method="post"&gt;<br>&lt;fieldset&gt;<br>&lt;div class="login_input"&gt;<br>&lt;input type="text" name="mb_id" maxlength="20" placeholder="아이디" required&gt;<br>&lt;input type="password" name="mb_password" maxlength="20" placeholder="비밀번호" required&gt;<br>&lt;/div&gt;<br>&lt;button type="submit" class="btn_login"&gt;LOGIN&lt;/button&gt;<br>&lt;/fieldset&gt;<br>&lt;/form&gt;<br>&lt;div class="login_links"&gt;<br>&lt;a href="&lt;?php echo G5_BBS_URL ?&gt;/register.php"&gt;회원가입&lt;/a&gt;<br>&lt;a href="&lt;?php echo G5_BBS_URL ?&gt;/password_lost.php"&gt;비밀번호 찾기&lt;/a&gt;<br>&lt;/div&gt;<br>&lt;/div&gt;<br>&lt;?php } ?&gt;<br>&lt;/div&gt;<br>&lt;/div&gt;<br><br>&lt;script&gt;<br>function flogin_submit(f) {<br>if (!f.mb_id.value) {<br>alert('아이디를 입력해 주세요.');<br>f.mb_id.focus();<br>return false;<br>}<br><br>if (!f.mb_password.value) {<br>alert('비밀번호를 입력해 주세요.');<br>f.mb_password.focus();<br>return false;<br>}<br><br>return true;<br>}<br>&lt;/script&gt;<br><br>&lt;script&gt;<br>$(document).ready(function(){<br>// 메뉴 위젯 숨기기<br>parent.$('.tf-menu-widget').hide();<br>parent.$('.tf-menu-mobile-btn').hide();<br>});<br><br>// 페이지 벗어날 때 다시 보이기<br>$(window).on("beforeunload", function(){<br>parent.$('.tf-menu-widget').show();<br>parent.$('.tf-menu-mobile-btn').show();<br>});<br>&lt;/script&gt;<br><br>&lt;?php include_once(G5_PATH.'/tail.sub.php'); ?&gt;</div>
</details><p><br></p></div>
  • 이전글 26.06.22

댓글목록

등록된 댓글이 없습니다.