미디어위키:Common.js: 두 판 사이의 차이

편집 요약 없음
편집 요약 없음
22번째 줄: 22번째 줄:
// 사이드바 업데이트
// 사이드바 업데이트
function updateSidebar() {
function updateSidebar() {
    if (!window.LANG || !window.LANG_NAMES || !window.CAT_LINKS) {
        setTimeout(updateSidebar, 100);
        return;
    }
     var langData = document.getElementById('clbi-lang-data');
     var langData = document.getElementById('clbi-lang-data');
     var currentLang = getCurrentLang();
     var currentLang = getCurrentLang();

2026년 4월 19일 (일) 08:35 판

importScript('MediaWiki:Lang.js');

$(function() {
    $('body').prepend('<div class="WW-bg" style="position:fixed;top:0px;left:0px;width:100%;height:100vh;"></div>');
});

// 페이지 전환 사운드
var transitionSound = new Audio('/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3');
transitionSound.volume = 0.6;

function playStaticSound() {
    transitionSound.currentTime = 0;
    transitionSound.play();
}

// 현재 언어 감지
function getCurrentLang() {
    var langData = document.getElementById('clbi-lang-data');
    return langData ? (langData.getAttribute('data-lang') || 'ko') : 'ko';
}

// 사이드바 업데이트
function updateSidebar() {
    if (!window.LANG || !window.LANG_NAMES || !window.CAT_LINKS) {
        setTimeout(updateSidebar, 100);
        return;
    }
    var langData = document.getElementById('clbi-lang-data');
    var currentLang = getCurrentLang();
    var t = (window.LANG && window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG['ko'];
    var names = (window.LANG_NAMES && window.LANG_NAMES[currentLang]) ? window.LANG_NAMES[currentLang] : window.LANG_NAMES['ko'];
    var cl = (window.CAT_LINKS && window.CAT_LINKS[currentLang]) ? window.CAT_LINKS[currentLang] : window.CAT_LINKS['ko'];
    var allLangs = langData ? {
        ko: langData.getAttribute('data-ko') || '',
        en: langData.getAttribute('data-en') || '',
        zh: langData.getAttribute('data-zh') || '',
        ja: langData.getAttribute('data-ja') || ''
    } : { ko: '', en: '', zh: '', ja: '' };

    $('#clbi-lang-current').text(names[currentLang]);
    var langHtml = '';
    $.each(['ko', 'en', 'zh', 'ja'], function(i, lang) {
        if (lang === currentLang) return;
        var target = allLangs[lang];
        if (target) {
            langHtml += '<div class="clbi-lang-link"><a href="/index.php/' + encodeURIComponent(target) + '">' + names[lang] + '</a></div>';
        } else {
            langHtml += '<div class="clbi-lang-wip">' + names[lang] + '</div>';
        }
    });
    $('#clbi-lang-list').html(langHtml);

    $('#clbi-title-language').text(t.language);
    $('#clbi-title-categories').text(t.categories);
    $('#clbi-title-links').text(t.links);
    $('#clbi-title-search a').text(t.search);
    $('#clbi-search-input').attr('placeholder', t.search + '...');
    $('#clbi-title-recent a').text(t.recentChanges);
    $('#clbi-title-guide').text(t.guide);
    $('#clbi-guide-link').text(t.getStarted);
    $('#clbi-title-playlist').text(t.playlist);

    $('#clbi-cat-main a').attr('href', '/index.php/' + cl.main);
    $('#clbi-cat-main .clbi-cat-label').text(t.mainMenu);
    $('#clbi-cat-nations a').attr('href', '/index.php/' + cl.nations);
    $('#clbi-cat-nations .clbi-cat-label').text(t.nations);
    $('#clbi-cat-corporations a').attr('href', '/index.php/' + cl.corporations);
    $('#clbi-cat-corporations .clbi-cat-label').text(t.corporations);
    $('#clbi-cat-military a').attr('href', '/index.php/' + cl.military);
    $('#clbi-cat-military .clbi-cat-label').text(t.military);
    $('#clbi-cat-history a').attr('href', '/index.php/' + cl.history);
    $('#clbi-cat-history .clbi-cat-label').text(t.history);
    $('#clbi-cat-personnel a').attr('href', '/index.php/' + cl.personnel);
    $('#clbi-cat-personnel .clbi-cat-label').text(t.personnel);

    $('#clbi-btn-contribution').text(t.contribution);
    $('#clbi-btn-talk').text(t.talk);
    $('#clbi-btn-watchlist').text(t.watchlist);
    $('#clbi-btn-preferences').text(t.preferences);
    $('#clbi-btn-logout').text(t.logout);
    $('#clbi-btn-login').text(t.login);

    var pageName = mw.config.get('wgPageName');
    $('.clbi-cat-btn').removeClass('clbi-cat-active');
    $.each(['main', 'nations', 'corporations', 'military', 'history', 'personnel'], function(i, key) {
        if (cl[key] && pageName === cl[key]) {
            $('#clbi-cat-' + key).addClass('clbi-cat-active');
        }
    });
}

// 대문 스타일
function applyMainPageStyle() {
    var pageName = mw.config.get('wgPageName');
    if (pageName === '대문') {
        $('.liberty-content-header').css('display', 'none');
        $('.mw-page-title-main').addClass('clbi-hide');
        $('.content-tools').css('display', 'none');
        $('.liberty-content-main').css('border-radius', '5px');
        if ($('#clbi-main-logo').length === 0) {
            $('.liberty-content').prepend(
                '<div id="clbi-main-logo" style="text-align:center;padding:20px 0;">' +
                '<img src="/index.php?title=특수:Redirect/file/Img-clbi-001.png" style="width:800px;height:auto;">' +
                '</div>'
            );
        }
    } else {
        $('.liberty-content-header').css('display', '');
        $('.mw-page-title-main').removeClass('clbi-hide');
        $('.content-tools').css('display', '');
        $('.liberty-content-main').css('border-radius', '');
        $('#clbi-main-logo').remove();
    }
    updateSidebar();
}

// 초기화 함수
function initSidebars() {
    // 편집 버튼들 붙이기
    var header = $('.liberty-content-header');
    var content = $('.liberty-content');
    if (header.length && content.length) {
        header.prependTo(content);
    }

    // 오른쪽 사이드바
    if ($('#clbi-right-sidebar').length === 0) {
        var username = mw.config.get('wgUserName');
        var isLoggedIn = username !== null;
        var avatarSrc = $('.profile-img').attr('src') || '/index.php?title=특수:Redirect/file/pfp-default.png';

        var userBox;
        if (isLoggedIn) {
            userBox =
                '<div class="clbi-right-box">' +
                '<div style="display:flex;flex-direction:column;align-items:center;padding:14px 14px 10px;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);">' +
                '<img src="' + avatarSrc + '" style="width:64px;height:64px;border-radius:5px;object-fit:cover;border:2px solid #854369;margin-bottom:8px;">' +
                '<a href="/index.php/사용자:' + username + '" style="font-size:13px;font-weight:700;color:#E2E2E2 !important;text-decoration:none !important;">' + username + '</a>' +
                '</div>' +
                '<div class="clbi-right-content" style="border-top:1px solid #2a2a2a;padding:8px;">' +
                '<a href="/index.php/특수:기여/' + username + '" class="clbi-user-btn" id="clbi-btn-contribution">기여</a>' +
                '<a href="/index.php/사용자_토론:' + username + '" class="clbi-user-btn" id="clbi-btn-talk">토론</a>' +
                '<a href="/index.php/특수:주시문서목록" class="clbi-user-btn" id="clbi-btn-watchlist">주시문서 목록</a>' +
                '<a href="/index.php/특수:환경설정" class="clbi-user-btn" id="clbi-btn-preferences">환경 설정</a>' +
                '<a href="/index.php?title=특수:로그아웃&returnto=대문" class="clbi-user-btn clbi-user-btn-logout" id="clbi-btn-logout">로그아웃</a>' +
                '</div></div>';
        } else {
            userBox =
                '<div class="clbi-right-box">' +
                '<div style="display:flex;flex-direction:column;align-items:center;padding:14px 14px 10px;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);">' +
                '<img src="/index.php?title=특수:Redirect/file/pfp-default.png" style="width:64px;height:64px;border-radius:5px;object-fit:cover;border:2px solid #854369;margin-bottom:8px;">' +
                '<span style="font-size:13px;font-weight:700;color:#E2E2E2;">Guest</span>' +
                '</div>' +
                '<div class="clbi-right-content" style="border-top:1px solid #2a2a2a;padding:8px;">' +
                '<a href="/index.php?title=특수:로그인&returnto=대문" class="clbi-user-btn" id="clbi-btn-login">로그인</a>' +
                '</div></div>';
        }

        var recentBox =
            '<div class="clbi-right-box">' +
            '<div class="clbi-right-title" id="clbi-title-recent"><span class="clbi-icon" style="--icon:var(--ic-ui-006)"></span> <a href="/index.php/특수:최근바뀜" style="color:#E2E2E2 !important;text-decoration:none !important;">최근 변경</a></div>' +
            '<div class="clbi-right-content" id="clbi-recent-list">불러오는 중...</div>' +
            '</div>';

        var spotifyBox =
            '<div class="clbi-right-box" style="overflow:hidden;">' +
            '<div class="clbi-right-title" id="clbi-title-playlist"><span class="clbi-icon" style="--icon:var(--ic-ui-003)"></span> 플레이리스트</div>' +
            '<iframe style="border-radius:0;width:100%;height:380px;border:none;" src="https://open.spotify.com/embed/playlist/32l4ke6djdQn8LoBp1ipR9?utm_source=generator&theme=0" allowfullscreen allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>' +
            '</div>';

        var sidebar =
            userBox +
            '<div class="clbi-right-box">' +
            '<div class="clbi-right-title" id="clbi-title-search"><span class="clbi-icon" style="--icon:var(--ic-ui-005)"></span> <a href="/index.php/특수:검색" style="color:#E2E2E2 !important;text-decoration:none !important;">검색</a></div>' +
            '<div class="clbi-right-content">' +
            '<input id="clbi-search-input" type="text" placeholder="검색...">' +
            '<button id="clbi-search-btn">GO</button>' +
            '</div></div>' +
            recentBox +
            '<div class="clbi-right-box">' +
            '<div class="clbi-right-title" id="clbi-title-guide"><span class="clbi-icon" style="--icon:var(--ic-ui-007)"></span> 가이드</div>' +
            '<div class="clbi-right-content">' +
            '<a href="/index.php/CLBI_Wiki/KR_시작하기_(CLBI)" id="clbi-guide-link">시작하기</a>' +
            '</div></div>' +
            spotifyBox;

        var wrapper = '<div id="clbi-right-sidebar">' + sidebar + '</div>';
        $('.content-wrapper').append(wrapper);

        $('#clbi-search-btn').click(function() {
            var query = $('#clbi-search-input').val();
            if (query) window.location.href = '/index.php?search=' + encodeURIComponent(query);
        });
        $('#clbi-search-input').keypress(function(e) {
            if (e.which == 13) $('#clbi-search-btn').click();
        });

        $.getJSON('/api.php?action=query&list=recentchanges&rclimit=5&rcprop=title|timestamp&format=json&rcnamespace=0&rctype=edit|new', function(data) {
            var items = data.query.recentchanges;
            var html = '';
            $.each(items, function(i, item) {
                var label = timeAgo(item.timestamp);
                html +=
                    '<div class="clbi-recent-item">' +
                    '<div class="clbi-recent-title-wrap">' +
                    '<a href="/index.php/' + encodeURIComponent(item.title) + '" class="clbi-recent-title">' + item.title + '</a>' +
                    '</div>' +
                    '<span class="clbi-recent-time">' + label + '</span>' +
                    '</div>';
            });
            $('#clbi-recent-list').html(html);

            $('#clbi-recent-list .clbi-recent-item').each(function() {
                var wrap = $(this).find('.clbi-recent-title-wrap');
                var title = $(this).find('.clbi-recent-title');
                var wrapW = wrap.width();
                var titleW = title[0].scrollWidth;
                if (titleW > wrapW + 20) {
                    var duration = titleW / 40;
                    title.css({
                        'animation': 'clbi-scroll ' + duration + 's linear infinite',
                        '--scroll-dist': '-' + (titleW - wrapW + 8) + 'px'
                    });
                }
            });
        }).fail(function() {
            $('#clbi-recent-list').html('불러오기 실패');
        });
    }

    // 왼쪽 사이드바
    if ($('#clbi-left-sidebar').length === 0) {
        var leftSidebar = '<div id="clbi-left-sidebar">' +
            '<div class="clbi-left-box">' +
            '<div class="clbi-left-title"><span class="clbi-icon" style="--icon:var(--ic-ui-001)"></span> <span id="clbi-title-language">언어</span></div>' +
            '<div class="clbi-left-content clbi-lang-box" id="clbi-lang-box">' +
            '<div class="clbi-lang-current" id="clbi-lang-current">한국어</div>' +
            '<div id="clbi-lang-list" style="display:none;"></div>' +
            '</div></div>' +
            '<div class="clbi-left-box">' +
            '<div class="clbi-left-title"><span class="clbi-icon" style="--icon:var(--ic-ui-002)"></span> <span id="clbi-title-categories">카테고리</span></div>' +
            '<div class="clbi-left-content clbi-cat-box">' +
            '<div class="clbi-cat-btn" id="clbi-cat-main"><a href="/index.php/대문"><div class="clbi-cat-text"><span class="clbi-cat-label">메인 메뉴</span><span class="clbi-cat-sub">MAIN MENU</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '<div class="clbi-cat-btn" id="clbi-cat-nations"><a href="/index.php/국가_및_조합"><div class="clbi-cat-text"><span class="clbi-cat-label">국가 및 조합</span><span class="clbi-cat-sub">NATIONS & FACTIONS</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '<div class="clbi-cat-btn" id="clbi-cat-corporations"><a href="/index.php/기업_및_공동체"><div class="clbi-cat-text"><span class="clbi-cat-label">기업 및 공동체</span><span class="clbi-cat-sub">CORPORATIONS & COMMUNITIES</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '<div class="clbi-cat-btn" id="clbi-cat-military"><a href="/index.php/군_정치집단"><div class="clbi-cat-text"><span class="clbi-cat-label">군, 정치집단</span><span class="clbi-cat-sub">MILITARY & POLITICS</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '<div class="clbi-cat-btn" id="clbi-cat-history"><a href="/index.php/역사적_사건"><div class="clbi-cat-text"><span class="clbi-cat-label">역사적 사건</span><span class="clbi-cat-sub">HISTORICAL EVENTS</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '<div class="clbi-cat-btn" id="clbi-cat-personnel"><a href="/index.php/인물"><div class="clbi-cat-text"><span class="clbi-cat-label">인물</span><span class="clbi-cat-sub">KEY PERSONNEL</span></div></a><div class="clbi-cat-arrow">▶</div></div>' +
            '</div></div>' +
            '<div class="clbi-left-box">' +
            '<div class="clbi-left-title"><span class="clbi-icon" style="--icon:var(--ic-ui-003)"></span> <span id="clbi-title-links">링크</span></div>' +
            '<div class="clbi-left-content clbi-link-box">' +
            '<ul>' +
            '<li><a href="https://discord.gg/ctaeJ9d3Q5" target="_blank">Discord</a></li>' +
            '<li><a href="https://www.youtube.com/@nxdsxn" target="_blank">YouTube</a></li>' +
            '<li><a href="https://x.com/nxd_sxn" target="_blank">X</a></li>' +
            '</ul>' +
            '</div></div>' +
            '</div>';

        $('.content-wrapper').prepend(leftSidebar);

        $(document).on('click', '#clbi-lang-current', function() {
            if ($('#clbi-lang-list').is(':hidden')) {
                $('#clbi-lang-current').css('margin-bottom', '8px');
                $('#clbi-lang-list').slideDown(200);
            } else {
                $('#clbi-lang-list').slideUp(200, function() {
                    $('#clbi-lang-current').css('margin-bottom', '0');
                });
            }
        });
    }

    applyMainPageStyle();
    mw.loader.using(['mediawiki.api']).then(function() { initProfile(); });
}

$(function() {
    setTimeout(function() {
        initSidebars();
    }, 100);
});

// SPA 네비게이션
$(function() {
    if (window._spaInitialized) return;
    window._spaInitialized = true;

    function isInternal(url) {
        var a = document.createElement('a');
        a.href = url;
        return a.hostname === window.location.hostname;
    }

    function shouldSkip(url) {
        return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout/);
    }

    function loadPage(url) {
        fetch(url)
            .then(function(res) { return res.text(); })
            .then(function(html) {
                var parser = new DOMParser();
                var doc = parser.parseFromString(html, 'text/html');
                var newContent = doc.querySelector('.liberty-content-main');
                var newTitle = doc.querySelector('.mw-page-title-main');
                var newHead = doc.querySelector('title');
                if (newContent) {
                    $('.liberty-content-main').html(newContent.innerHTML);
                    $('body').removeClass('page-loading');
                }
                if (newTitle) {
                    $('.mw-page-title-main').html(newTitle.innerHTML);
                }
                if (newHead) {
                    document.title = newHead.textContent;
                }
                var newHeader = doc.querySelector('.liberty-content-header');
                if (newHeader) {
                    $('.liberty-content-header').html(newHeader.innerHTML);
                }
                var newPageName = doc.querySelector('html').getAttribute('data-mw-requested-url') ||
                    url.replace(/.*\/index\.php\//, '').replace(/.*\/wiki\//, '').split('?')[0];
                newPageName = decodeURIComponent(newPageName);
                mw.config.set('wgPageName', newPageName);
                window.scrollTo(0, 0);
                $('#clbi-lang-list').hide();
                $('#clbi-lang-current').css('margin-bottom', '0');
                mw.hook('wikipage.content').fire($('.liberty-content-main'));
                applyMainPageStyle();
            });
    }

    $(document).on('click', 'a', function(e) {
        var href = $(this).attr('href');
        if (!href) return;
        if (!isInternal(href)) return;
        if (shouldSkip(href)) return;
        if (href.startsWith('#')) return;

        e.preventDefault();
        playStaticSound();
        $('body').addClass('page-loading');
        history.pushState(null, '', href);
        loadPage(href);
    });

    window.addEventListener('popstate', function() {
        loadPage(window.location.href);
    });
});

// 시간 계산 함수
function timeAgo(timestamp) {
    var now = new Date();
    var date = new Date(timestamp);
    var diff = Math.floor((now - date) / 1000);

    if (diff < 60) return diff + '초 전';
    if (diff < 3600) return Math.floor(diff / 60) + '분 전';
    if (diff < 86400) return Math.floor(diff / 3600) + '시간 전';
    return Math.floor(diff / 86400) + '일 전';
}

// 펼접 토글
$(function() {
    $(document).on('click', '.toggleBtn', function() {
        var targetId = $(this).data('target');
        var target = $('#' + targetId);
        var scrollY = window.scrollY;

        if (target.hasClass('folding-open')) {
            target.css('max-height', target[0].scrollHeight + 'px');
            target[0].offsetHeight;
            target.css('max-height', '0px');
            target.removeClass('folding-open');
        } else {
            target.css('max-height', '0px');
            target[0].offsetHeight;
            target.css('max-height', target[0].scrollHeight + 'px');
            target.addClass('folding-open');
        }

        window.scrollTo(0, scrollY);
    });
});

// ========== 프로필 시스템 ==========
function initProfile() {
    var ns = mw.config.get('wgNamespaceNumber');
    var title = mw.config.get('wgTitle');

    if (ns === 2) {
        var profileUser = title.split('/')[0];
        renderProfile(profileUser);
    }
    if (mw.config.get('wgCanonicalSpecialPageName') === 'Preferences') {
        addProfileTab();
    }
}

function renderProfile(username) {
    var api = new mw.Api();
    api.get({
        action: 'query',
        list: 'users',
        ususers: username,
        usprop: 'editcount',
    }).then(function(data) {
        var user = data.query.users[0];
        var contentEl = document.getElementById('mw-content-text');
        if (!contentEl) return;
        var pageContent = contentEl.querySelector('.mw-parser-output') || contentEl;
        injectProfileCard(username, user, pageContent);
    });
}

function injectProfileCard(username, userData, container) {
    var isOwnPage = mw.config.get('wgUserName') === username;
    var editCount = (userData && userData.editcount) ? userData.editcount : 0;
    var editBtn = isOwnPage
        ? '<a href="/index.php?title=특수:Preferences#mw-prefsection-clbi-profile" class="clbi-profile-edit-btn">프로필 수정</a>'
        : '';

    var card = document.createElement('div');
    card.className = 'clbi-profile-card';
    card.innerHTML =
        '<div class="clbi-profile-header">' +
            '<div class="clbi-profile-avatar">' +
                '<img src="/index.php?title=특수:Redirect/file/Pfp-' + username + '.png&width=120"' +
                ' onerror="this.src=\'/index.php?title=특수:Redirect/file/Pfp-default.png&width=120\'"' +
                ' alt="' + username + '">' +
            '</div>' +
            '<div class="clbi-profile-info">' +
                '<h2 class="clbi-profile-username">' + username + '</h2>' +
                '<div class="clbi-profile-name" data-field="name"></div>' +
                '<div class="clbi-profile-role" data-field="role"></div>' +
                '<div class="clbi-profile-discord" data-field="discord"></div>' +
                '<div class="clbi-profile-bio" data-field="bio"></div>' +
                '<div class="clbi-profile-badges" data-field="badges"></div>' +
            '</div>' +
            editBtn +
        '</div>' +
        '<div class="clbi-profile-stats">' +
            '<div class="clbi-profile-stat">' +
                '<span class="clbi-stat-value">' + editCount + '</span>' +
                '<span class="clbi-stat-label">수정 횟수</span>' +
            '</div>' +
        '</div>';

    container.insertBefore(card, container.firstChild);
    loadProfileFields(username, card);
}

function loadProfileFields(username, card) {
    var currentUser = mw.config.get('wgUserName');
    if (currentUser !== username) {
        updateProfileFields(card, { name: '', discord: '', role: '', bio: '', badges: '' });
        return;
    }
    var api = new mw.Api();
    api.get({
        action: 'query',
        meta: 'userinfo',
        uiprop: 'options',
    }).then(function(data) {
        var opts = data.query.userinfo.options;
        updateProfileFields(card, {
            name: opts['clbi-profile-name'] || '',
            discord: opts['clbi-profile-discord'] || '',
            role: opts['clbi-profile-role'] || '',
            bio: opts['clbi-profile-bio'] || '',
            badges: opts['clbi-profile-badges'] || '',
        });
    });
}

function updateProfileFields(card, data) {
    var nameEl = card.querySelector('[data-field="name"]');
    var roleEl = card.querySelector('[data-field="role"]');
    var discordEl = card.querySelector('[data-field="discord"]');
    var bioEl = card.querySelector('[data-field="bio"]');
    var badgesEl = card.querySelector('[data-field="badges"]');

    if (nameEl) nameEl.textContent = data.name || '';
    if (roleEl) roleEl.textContent = data.role || '';
    if (discordEl && data.discord) discordEl.textContent = '디스코드: ' + data.discord;
    if (bioEl) bioEl.textContent = data.bio || '';
    if (badgesEl && data.badges) {
        var badges = data.badges.split(',');
        var html = '';
        for (var i = 0; i < badges.length; i++) {
            html += '<span class="clbi-badge">' + badges[i].trim() + '</span>';
        }
        badgesEl.innerHTML = html;
    }
}

function addProfileTab() {
    var api = new mw.Api();
    api.get({
        action: 'query',
        meta: 'userinfo',
        uiprop: 'options',
    }).then(function(data) {
        var opts = data.query.userinfo.options;
        var isAdmin = mw.config.get('wgUserGroups') && mw.config.get('wgUserGroups').indexOf('sysop') !== -1;

        var tabList = document.querySelector('#preftoc');
        if (!tabList) return;

        var tab = document.createElement('li');
        tab.id = 'preftab-clbi-profile';
        tab.innerHTML = '<a href="#mw-prefsection-clbi-profile">프로필</a>';
        tabList.appendChild(tab);

        var prefsForm = document.querySelector('#mw-prefs-form');
        if (!prefsForm) return;

        var badgesRow = isAdmin
            ? '<div class="clbi-pref-row">' +
                '<label>훈장 (관리자 전용, 쉼표로 구분)</label>' +
                '<input type="text" id="clbi-pref-badges" value="' + (opts['clbi-profile-badges'] || '') + '" placeholder="훈장1, 훈장2">' +
              '</div>'
            : '';

        var section = document.createElement('div');
        section.id = 'mw-prefsection-clbi-profile';
        section.className = 'prefsection';
        section.style.display = 'none';
        section.innerHTML =
            '<h2>프로필 설정</h2>' +
            '<div class="clbi-prefs-profile">' +
                '<div class="clbi-pref-row">' +
                    '<label>이름</label>' +
                    '<input type="text" id="clbi-pref-name" value="' + (opts['clbi-profile-name'] || '') + '" placeholder="표시될 이름">' +
                '</div>' +
                '<div class="clbi-pref-row">' +
                    '<label>디스코드 ID</label>' +
                    '<input type="text" id="clbi-pref-discord" value="' + (opts['clbi-profile-discord'] || '') + '" placeholder="username">' +
                '</div>' +
                '<div class="clbi-pref-row">' +
                    '<label>역할</label>' +
                    '<input type="text" id="clbi-pref-role" value="' + (opts['clbi-profile-role'] || '') + '" placeholder="예: 시나리오 작가">' +
                '</div>' +
                '<div class="clbi-pref-row">' +
                    '<label>자기소개</label>' +
                    '<textarea id="clbi-pref-bio" placeholder="간단한 자기소개">' + (opts['clbi-profile-bio'] || '') + '</textarea>' +
                '</div>' +
                badgesRow +
                '<button type="button" id="clbi-pref-save">저장</button>' +
                '<span id="clbi-pref-status"></span>' +
            '</div>';
        prefsForm.appendChild(section);

        tab.querySelector('a').addEventListener('click', function(e) {
            e.preventDefault();
            var secs = document.querySelectorAll('.prefsection');
            for (var i = 0; i < secs.length; i++) secs[i].style.display = 'none';
            var tabs = document.querySelectorAll('#preftoc li');
            for (var j = 0; j < tabs.length; j++) tabs[j].classList.remove('selected');
            section.style.display = '';
            tab.classList.add('selected');
        });

        document.getElementById('clbi-pref-save').addEventListener('click', function() {
            var statusEl = document.getElementById('clbi-pref-status');
            statusEl.textContent = '저장 중...';
            var fields = ['name', 'discord', 'role', 'bio'];
            if (isAdmin) fields.push('badges');
            var promises = [];
            for (var i = 0; i < fields.length; i++) {
                var el = document.getElementById('clbi-pref-' + fields[i]);
                if (!el) continue;
                promises.push(api.postWithToken('csrf', {
                    action: 'options',
                    optionname: 'clbi-profile-' + fields[i],
                    optionvalue: el.value,
                }));
            }
            $.when.apply($, promises).then(function() {
                statusEl.textContent = '✓ 저장됨';
                setTimeout(function() { statusEl.textContent = ''; }, 2000);
            }).fail(function() {
                statusEl.textContent = '저장 실패';
            });
        });
    });
}
// ========== 프로필 시스템 끝 ==========